Mittels der bisher vorgestellten Programmierfunktionalität ist es ohne weiteres möglich, das Spiel „Schiffe versenken" zu implementieren, wenn sämtliche Ein- und Ausgaben über die Kommandozeilenschnittstelle vorgenommen werden. Offensichtlich ist aber eine derartige Implementierung mehr als ineffizient, da sich der Benutzer bspw. merken muss, wo er seine Schiffe platziert hat. Einer der großen Vorteile von Java ist es, dass diese Sprache weit reichende Möglichkeiten zur Verarbeitung von Grafiken, Bildern, Audio und Benutzerschnittstellen zur Verfügung stellt – eine wesentliche Voraussetzung für die Implementierung des Spiels. In Java steht zur Implementierung von Benutzeroberflächen das Abstract Windowing Toolkits (AWT) zur Verfügung. Grundsätzlich beinhalten Benutzeroberflächen eine statische und eine dynamische Funktionalität. Während sichtbare Elemente wie bspw. Bilder oder Knöpfe zunächst einen statischen Charakter aufweisen, kommt eine dynamische Funktionalität dann ins Spiel, wenn Elemente eine Funktion beinhalten. Während ein Bild lediglich betrachtet werden kann, kann ein Knopf bedient werden. Das Drücken eines Knopfs löst dann ein Ereignis, einen sog. Event, aus. Ziel dieses Teilkapitels ist die Darstellung der statischen Funktionalität, wohingegen die Verarbeitung von Events im nächsten Teilkapitel erläutert wird. In diesem Teil des Buches wird daher zuerst erklärt, wie Benutzeroberflächen zu entwickeln sind. Diese sind wohlgemerkt ohne dynamische Komponenten zunächst nutzlos. Eine brauchbare Implementierung kann daher nur erfolgen, wenn die Kenntnisse beider Teilkapitel eingesetzt werden. Die Beispiele, die im Folgenden angegeben werden, beziehen sich grundsätzlich auf Applets. Eine Unterscheidung zwischen Applet und Application macht in diesem Zusammenhang wenig Sinn, da die notwendigen Konzepte für beide Anwendungsarten identisch gültig sind. Beachtet man die bereits beschriebenen Unterschiede zwischen Applet und Application, so kann jede der beschriebenen Benutzeroberflächen problemlos auch für eine Application angewendet werden. Zuerst wird erläutert, wie in Java Grafiken, Schriftarten und Farben, also die Grundbausteine von Benutzeroberflächen, verwendet werden. Grafiken in Java Grundsätzlich kann man ein Applet als eine Art von Container betrachten, der Platz für grafische Inhalte bietet. Die meisten Routinen, die zur Erzeugung von Grafikobjekten notwendig sind, sind in der Klasse Graphics enthalten, die Teil des AWT (Klasse java.awt) ist. Alle Applets, die Grafik verwenden, müssen daher diese Klasse importieren. Ein Grundgerüst, das in jedem derartigen Applet zu finden ist, lautet: import java.awt.Graphics; public class Beispiel extends java.applet.Applet { public void paint (Graphics screen) { //Aktion } } Die Methode paint, die Teil der Klasse Graphics ist, realisiert Bildschirmausgaben derart, dass eine beliebige Ausgabe durch ein Überschreiben der Methode paint implementiert wird. Hierzu ist es notwendig, ein Graphics-Objekt zu erzeugen, das die notwendigen Routinen zur Generierung zur Verfügung stellt. Das folgende Beispiel erzeugt bspw. die Ausgabe „Willkommen in Java". Die Parameter, die die dazu notwendige Methode drawString erwartet, sind ein String und die Bildschirmkoordinaten, an denen die Ausgabe erfolgen soll. Es sei angemerkt, dass drawString Teil der Klasse Graphics ist, was auch an der Referenzierung über die Variable screen erkennbar wird. import java.awt.Graphics; public class Beispiel extends java.applet.Applet { public void paint (Graphics screen) { screen.drawString("Willkommen in Java", 10,10); } } Jedes Mal, wenn ein Applet angezeigt wird, wird die paint-Methode durchlaufen und dadurch die gewünschte Ausgabe erzeugt. Das von Java verwendete Koordinatensystem ist derart aufgebaut, dass der Ursprung (Koordinate (0, 0)) in der linken oberen Ecke angesiedelt ist. x-Koordinaten, die in Koordinaten jeweils links angegeben werden, spezifizieren die horizontale Position eines Elements, y-Koordinaten entsprechend die vertikale Position. Maßeinheit ist in Java grundsätzlich das Picture Element (Pixel). Zum Zeichnen grafischer Objekte unterscheidet Java zwischen Methoden, die Objekte zeichnen und solchen, die vorliegende Objekte mit einer Farbe ausfüllen. Java verwendet die folgenden Routinen zum Zeichnen von Objekten: drawLine() zum Zeichnen von Linien. drawLine erwartet vier Argumente: die x- und y-Position des Ausgangspunkts der Linie und respektive die x- und y-Position des Endpunkts. Die folgende Zeile erzeugt bspw. eine Linie von (0, 0) nach (100, 100). Das entsprechende Applet ist leicht zu erzeugen, wenn die Zeile in die paint-Methode aufgenommen wird. screen.drawLine(0,0,100,100); drawRect() und fillRect() zum Zeichnen bzw. Ausfüllen von Rechtecken. Die Argumente, die drawRect erwartet, sind die Koordinaten der linken oberen Ecke des Rechtecks und dessen Breite und Höhe in Pixeln. Zum Erzeugen eines mit Farbe gefüllten Rechtecks wird die Methode fillRect verwendet, die dieselben Argumente verwendet. Das folgende Beispiel zeigt die Verwendung von drawRect und fillRect. Hierbei wird jeweils ein Rechteck erzeugt, dessen linke obere Ecke am Punkt (0,0) ist und dessen Höhe und Breite 50 Pixel beträgt. screen.drawRect(0,0,50,50); screen.fillRect(0,0,50,50); drawRoundRect() und fillRoundRect() zum Zeichnen bzw. Ausfüllen abgerundeter Rechtecke. Die Methoden drawRoundRect und fillRoundRect arbeiten analog wie drawRect und fillRect, erwarten aber zwei zusätzliche Parameter: Die Breite und Höhe der Ecken, die abgerundet werden. Je größer dieser Bereich ist, desto runder wird das Rechteck. Im Extremfall wird derart ein Kreis erzeugt. Das folgende Beispiel illustriert die Verwendung dieser Funktionen. Hierbei werden dieselben Rechtecke wie im vorhergehenden Beispiel erzeugt, die hier aber mit einer Höhe und Breite von jeweils 10 Pixeln abgerundet werden. screen.drawRoundRect(0,0,50,50,10,10); screen.fillRoundRect(0,0,50,50,10,10); drawPolygon() und fillPolygon() zum Zeichnen bzw. Ausfüllen von Polygonen. Polygone, Flächen mit einer beliebigen Anzahl von Ecken, können mit den Funktionen drawPolygon gezeichnet bzw. mit fillPolygon ausgefüllt werden. Die Koordinaten werden entweder als zwei Arrays oder als Polygon-Objekt angegeben. Werden zwei Arrays verwendet, so enthält der erste die x- und der zweite entsprechend die y-Koordinaten. Ein Polygon-Objekt kann erzeugt werden, indem ebenfalls die angeführten zwei Arrays verwendet werden. Weiterhin muss die Anzahl der Punkte eines Polygons angegeben werden. Man kann ein Polygon entweder manuell als geschlossenen Linienzug erzeugen, indem der letzte Punkt gleich dem ersten ist. Wird dies nicht vorgenommen, so werden die Linienzüge automatisch geschlossen. Polygone können auf zwei Arten erzeugt werden: Entweder wird mit der Anweisung Polygon p = new Polygon() ein neues Polygon erzeugt, dem anschließend mit der Anweisung p.addPoint (x,y) Punkte mit den jeweiligen x- und y-Koordinaten hinzugefügt werden, oder zwei Arrays, die die x- und y-Koordinaten enthalten, werden generiert und zusammen mit der Anzahl der Punkte an den Konstruktor übergeben. Ein Beispiel, in dem ein Quadrat erzeugt wird, ist: int x[] = {0,10,10,0}; int y[] = {0,0,10,10}; int punkte = x.length; Polygon p = new Polygon(x, y, punkte); Hierbei wurden zunächst zwei Arrays mit den x- und y-Koordinaten angelegt. Anschließend wurde die Anzahl der Punkte in der Variablen punkte gespeichert und das Polygon erzeugt. Sollen einem Polygon nun weitere Punkte hinzugefügt werden, so wird die Methode addPoint, die Teil der Klasse Polygon ist, verwendet. addPoint erwartet die x- und y-Koordinate des hinzuzufügenden Punkts als Argument. Das Kommando p.addPoint(20,20) fügt daher dem oben beschriebenen Polygon den Punkt (20,20) hinzu. Nachdem ein Polygon erzeugt wurde, kann es entweder mit drawPolygon oder mit fillPolygon angezeigt werden. Grundsätzlich ist zu beachten, dass zur Verwendung von Polygonen die Klasse java.awt.Polygon zu importieren ist. Das folgende Beispiel illustriert die Anzeige des oben generierten Polygons. int x[] = {0,10,10,0}; int y[] = {0,0,10,10}; int punkte = x.length; Polygon p = new Polygon(x, y, punkte); p.addPoint(20,20); screen.drawPolygon(p); // alternativ screen.fillPolygon(p); drawOval() und fillOval() zum Zeichnen bzw. Ausfüllen von Ovalen. Die Methoden drawOval und fillOval werden dazu verwendet, Ovale (ausgefüllt) zu zeichnen. Beide Methoden erwarten vier Argumente: Die x- und y-Koordinate des Ovals und dessen Breite und Höhe.Wie auch bereits bei abgerundeten Rechtecken spezifiziert die übergebene Koordinate die linke obere Ecke der Box minimaler Größe, die das Oval vollständig ohne Überschneidung umschließen würde. Es sei angemerkt, dass mit diesen Funktionen auch Kreise gezeichnet werden können, wenn die Höhe gleich der Breite ist. Das folgende Beispiel zeichnet ein Oval und einen ausgefüllten Kreis: screen.drawOval(0,0,100,200); screen.fillOval(100,100,100,100); drawArc() und fillArc() zum Zeichnen bzw. Ausfüllen von Kreisbögen. Die Methoden drawArc und fillarc erzeugen (ausgefüllte) Kreisbögen, also Teile eines Ovals. Beide Methoden erwarten sechs Argumente: Dieselben vier wie auch bereits für Ovale und zusätzlich die Winkelangabe, bei der der Ovalausschnitt beginnt und die Anzahl der Winkelgrade, die der Ovalausschnitt einnimmt. Winkelangaben werden in Java zwischen 0 und 360 Grad entgegen des Uhrzeigersinns gerechnet. Dieses Vorgehen ist in Abb. 4-6 illustriert. fillArc füllt einen Ovalausschnitt so aus, dass die Endpunkte der Außenlinie des Ovals mit dem Mittelpunkt verbunden werden. Eine derartige Darstellung findet sich häufig bei Kuchendiagrammen. Das folgende Beispiel erzeugt einen Ovalausschnitt für ein Oval der Breite 100 und der Höhe 200 mit linkerer oberer Ecke (0,0), Winkelbeginn 0 Grad und Gradlänge 90 Grad. Das zweite Beispiel stellt dasselbe Oval ausgefüllt dar. screen.drawArc(0,0,100,200,0,90); screen.fillArc(0,0,100,200,0,90; Abb. 4.6: Winkelangaben in Java Um die Implementierung von Grafiken zu beschleunigen, stehen in Java zusätzlich die Methoden copyArea und clearRect zur Verfügung. Mittels copyArea können rechteckige Bereiche eines Applet-Fensters auf andere Bereiche dieses Fensters kopiert werden. copyArea erwartet sechs Argumente: die x- und y-Koordinate der linken oberen Ecke der Region, die zu kopieren ist, die Breite und Höhe der zu kopierenden Region und die horizontale und vertikale Distanz, um die die zu kopierende Region bewegt werden soll. Das folgende Beispiel kopiert eine Region der Größe 50x50 Pixel, die sich derzeit an der Position (60,60) (linke obere Ecke) befindet, um 20 Pixel nach links und um 30 Pixel nach unten: screen.copyArea(60,60,50,50,-20, 30); Mittels der clearRect-Methode wird ein Bildschirmbereich mit der Farbe gefüllt, die der Hintergrundfarbe des Applets entspricht. clearRect erwartet dieselben vier Parameter wie drawRect. Soll bspw. der ganze Bildschirmbereich gelöscht werden, so kann die getSize-Methode verwendet werden, die die Breite und Höhe des Bereichs als Ergebnis zurückgibt. Das folgende Beispiel zeigt, wie der gesamte Bildschirmbereich zu löschen ist. screen.clearRect(0,0,getSize().width, getSize().height); Zum Abschluss dieses Abschnitts wird ein Beispiel angegeben, das ein Spielfeld für das Spiel „Schiffe versenken" erzeugt. Der Leser sollte mittlerweile über die notwendigen Kenntnisse verfügen, dieses Beispiel zu verstehen. import java.awt.Graphics; public class Spielfeld extends java.applet.Applet { public void paint (Graphics screen) { int i; // Erzeuge Quadrat als Spielfeldbegrenzung screen.drawRect(10,10,300,300); // Erzeuge horizontale Linien for (i = 1; i < 10; i++) screen.drawLine(10, 10+i*30,310, 10+i*30); // Erzeuge vertikale Linien for (i = 1; i < 10; i++) screen.drawLine(10+i*30, 10,10+i*30, 310); } } Farbsetzung in Anwendungen Zur Beschreibung von Farben werden üblicherweise sog. Farbräume verwendet, bspw. RGB, HSV oder YCrCb [Stei98]. Java unterstützt den RGB (rot–grün–blau)-Farbraum, in dem Farben als Kombination aus rot, grün und blau angegeben werden. Für jede Farbkomponente steht hierbei ein Byte zur Verfügung, mit dem 256 verschiedene Werte spezifizierbar sind. Schwarz wird bspw. als (0,0,0) und weiß als (255,255,255) angegeben. Bei Weiß sind daher alle Farben in maximaler Intensität vorhanden. Abb. 4.7: Screenshot des Spielfelds Java verwendet die Klassen Color und ColorSpace als Teil des Packages java.awt, um Anwendungen Farben zuzuweisen. Während Color die Verarbeitung von Farben betrifft, können mit ColorSpace auch andere Farbräume angegeben werden. In diesem Buch soll jedoch stets der RGB-Farbraum verwendet werden. Zum Setzen von Farben muss entweder ein Color-Objekt generiert werden, das eine Farbe repräsentiert, oder eine der Standardfarben verwendet werden, die Teil der Klasse Color sind. Der Konstruktor dieser Klasse kann auf zwei verschiedene Arten aufgerufen werden: Als Kombination eines Tripels von Ganzzahlen (Integers), die die RGB-Komponenten angeben oder als Tripel aus Fließkommazahlen, die den prozentualen Anteil des Zahlenbereichs von 0 bis 255 angeben. //RGB-Farbe Schwarz Color farbe1 = new Color (0F,0F,0F); // RGB-Farbe Weiss Color farbe2 = new Color (1F, 1F, 1F); Im obigen Beispiel wurde jeweils ein F an die eigentliche Zahl angehängt, da es sich um Fließkommazahlen (floats) handelt. Farben können nun mit Hilfe der Methode setColor gesetzt werden, die ebenfalls Teil der Klasse Graphics ist und daher in einer paint-Methode Anwendung findet. Tab. 4-1 gibt einige gebräuchliche Farbkombinationen an.

Farbe | RGB-Kombination
Schwarz | (0,0,0)
Weiss | (255,255,255)
Rot | (255,0,0)
Blau | (0,0,255)
Grün | (0,255,0)
Gelb | (255,255,0)
Orange | (255,200,0)
Lila | (255,0,255)

Tab. 4.1: RGB-Kombinationen Das folgende Beispiel illustriert, wie eine Farbe gesetzt wird. Hierbei werden auch der Vorder- und der Hintergrundbereich auf verschiedene Farben gesetzt, wozu die Methoden setForeground und setBackground verwendet werden, die Teil der Klasse Applet sind. // Orange Color farbe = new Color (255,200,0); screen.setColor(farbe); //Schwarze Farbe als Standardkombination screen.setColor(Color.black); //Vorder- und Hintergrund setForeground(Color.white); setBackground(Color.black); Meist werden Vorder- und Hintergrund eines Applets in der init-Methode gesetzt, da sich diese Farbe normalerweise im Lebenszyklus des Applets nicht ändert. Soll herausgefunden werden, welche Farbe ein Objekt hat, so kann die Methode getColor verwendet werden, die Teil der Klasse Graphics ist, bzw. getForeground und getBackground als Teil der Klasse Applet. Das folgende Beispiel legt dar, wie die Vordergrundfarbe auf den Wert eingestellt wird, den auch der Hintergrund hat. //Gleiche Vordergrund- und Hintergrundfarbe einstellen setForeground(getBackground); Schriftarten in der Benutzeroberfläche In den meisten Benutzeroberflächen sind Texte ein wesentlicher Informationsträger. In Java kann die Klasse Font als Teil der Klasse java.awt dazu verwendet werden, die benötigte Schriftart (sog. Font) und deren Eigenschaften einzustellen. Ein Font wird immer mit Name, Stil und Größe in Punkten spezifiziert. Zur Erzeugung eines Font-Objekts muss daher der Name des Fonts, dessen Stil und die Punktgröße an den Konstruktor übergeben werden. Beispiele für Font-Namen sind Times New Roman oder Arial. Stile sind bspw. Normal, Fett oder Kursiv. Es ist daher logisch, dass Bestandteile der Klasse Font die Kombinationen Font.PLAIN (normal), Font.BOLD (fett) und Font.ITALIC (kursiv) sind. Im folgenden Beispiel wird ein Font erzeugt, der eine kursive fettgedruckte Serifenschrift der Größe 10 Punkt verwendet. //Kursive fettgedruckte Serifenschrift 10 Punkt Font f = new Font("Serif", Font.ITALIC + Font.BOLD, 10); Nachdem ein Font initialisiert worden ist, kann er mit der Methode setFont, die Teil der Klasse Graphics ist, einer Bildschirmdarstellung zugewiesen werden: // Setzen des Fonts screen.setFont(f); Meist erfolgen diese Anweisungen, bevor ein Text auch tatsächlich ausgegeben wird. Die Textausgabe erfolgt in einem Applet mit der Methode drawString als Teil der Klasse Graphics (wenn der Text nicht Teil einer anderen Komponente, wie bspw. eines Knopfs ist). drawString erwartet als Argumente einen String und die Position, an der der Text ausgegeben werden soll. Hierbei gibt die x-Koordinate die linke Begrenzung des Textes an und die y-Koordinate die Basislinie, auf der der Text aufsetzt. Zusammengesetzt sieht das Beispiel wie folgt aus: //Vollstaendige Textausgabe public void paint (Graphics screen) { Font f = new Font("Serif", Font.ITALIC + Font.BOLD, 10); screen.setFont(f); screen.drawString("Willkommen in Java!", 10,10); } Es kommt ab und an vor, dass man eine Font-Größe wählt, die nicht verfügbar ist. Um dieses Problem zu vermeiden, kann die Klasse FontMetrics verwendet werden, die Informationen über einen Font zur Verfügung stellt. Hierzu muss zuerst mittels getFontMetrics ein Objekt erzeugt werden. getFontMetrics akzeptiert ein Argument: Ein Objekt vom Typ Font. Anschließend können die folgenden Methoden aufgerufen werden: getHeight() gibt die Höhe eines Fonts in Punkten wieder. stringWidth(String) ermittelt die Länge eines übergebenen Strings in Pixeln. charWidth(char) ermittelt analog die Länge eines einzelnen Zeichens. Das folgende Beispiel illustriert die Verwendung dieser Methoden. //Font-Eigenschaften public void paint(Graphics screen) { int h, sw; String s = "Hallo"; Font f = new Font("Serif", Font.ITALIC + Font.BOLD, 10); screen.setFont(f); FontMetrics fm = getFontMetrics(f); h = fm.getHeight(); sw = fm.stringWidth(s); } Grafikoperationen in Java2D Seit der Version 1.2 von Java stehen eine Menge erweiternder Routinen zur Verfügung, mit denen hochqualitative Grafiken und Texte erzeugt werden können. Dies sind bspw. verschiedene Füllmuster oder Linienarten. Weiterhin wurde das Konzept des Koordinatensystems signifikant erweitert. Man unterscheidet nun zwischen einem globalen Koordinatensystem, das dem bisher verwendeten System entspricht und einem benutzerdefinierten System, das frei wählbar ebenfalls mit x- und y-Koordinaten arbeitet. Beide Arten sind allerdings nach wie vor kartesische Systeme; affine Systeme können nicht verwendet werden. Wird nichts anderes angegeben, so sind beide Koordinatensysteme identisch. Der Benutzer kann allerdings ein eigenes System angeben, in dem die linke obere Ecke nicht länger den Koordinaten (0,0) des globalen Systems entspricht. Auch Drehungen der x- und der y-Achse sind hier möglich. Bisher wurde zur Verwaltung grafischer Operationen ein Graphics-Objekt verwendet. In Java2D wird dieses Konzept um ein Graphics2D-Objekt erweitert, das durch ein Casting eines Graphics-Objekts generiert werden kann und muss, da alle Java2D-Routinen ausschließlich mit Graphics2D-Objekten arbeiten. Auch Graphics2D ist Teil des Packages java.awt. Das folgende Beispiel illustriert dies. //Java2D public void paint(Graphics screen) { Graphics2D screen2D = (Graphics2D) screen; } Anschließend muss die Art und Weise angegeben werden, wie ein Objekt erzeugt werden soll, bspw. die Art der Farbe, der Linienstärke oder auch des Füllmusters. Farben in Java2D Farben werden in Java2D wie bisher auch angegeben, bspw. als screen2d.setColor(Color.black);. Linien in Java2D Als Erweiterung zum bisherigen Konzept von Linien, die einen Pixel breit sind, kann die Stärke in Java2D frei gewählt werden. Hierzu wird zuerst der Konstruktor BasicStroke aufgerufen, der drei Argumente akzeptiert: die Linienbreite, die als float-Wert angegeben wird, die Art des Linienabschlusses (schräg, rund oder gerade), die als Integer-Wert oder als CAP_BUTT, CAP_ROUND oder CAP_SQUARE angegeben wird und die Art, in der zwei Linienzüge verbunden werden (spitz, rund oder abgeflacht). Diese kann als Integer-Wert oder als JOIN_MITER, JOIN_ROUND oder JOIN_BEVEL angegeben werden. Abb. 4-8 illustriert die verschiedenen Linienarten. Das folgende Beispiel stellt die Erzeugung einer Linie dar, die abgerundet endet und mit weiteren Linien abgerundet verbunden wird. Die Linienstärke ist hierbei 3 Pixel. In der folgenden Anweisung wird die Bildschirmumgebung mittels der Methode setStroke auf diese Werte eingestellt. //Festlegung der Linieneigenschaften BasicStroke stift = BasicStroke(3.0f, BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND); screen2D.setStroke(stift); Abb. 4.8: Linienarten in Java2D Nach der Angabe der Linienart, die in Grafiken angewendet werden soll, kann der Entwickler auch das Muster, mit dem Flächen gefüllt werden sollen, näher spezifizieren. Dies kann entweder eine einzige Farbe sein (wie bisher auch), ein Farbverlauf oder eine Textur. Diese Möglichkeiten sind in Abb. 4-9 dargestellt. Abb. 4.9: Füllmuster in Java2D Um in Java2D ein Füllmuster festzulegen, kann die GradientPoint-Methode verwendet werden, die sechs Argumente erwartet: Koordinaten x1 und y1, bei denen eine Farbe Farbe1 beginnt, und Koordinaten x2 und y2, bei denen der Farbverlauf mit Farbe2 endet. Alle Koordinaten müssen hierbei vom Typ float sein. Zusätzlich kann als siebtes Argument angegeben werden, ob eine Textur (zyklisches Füllen) oder ein Verlauf (azyklisches Füllen) erzeugt werden soll. Hierzu ist entweder true (zyklisches Füllen) oder false (azyklisches Füllen) anzuhängen. Da die Einstellung false aber standardmäßig angenommen wird, kann der siebte Parameter entfallen, wenn Verläufe verwendet werden sollen. Das folgende Beispiel verdeutlicht die Verwendung von GradientPoint, indem der Füllstil auf einen Verlauf von Schwarz nach Weiß eingestellt wird, der bei den Koordinaten (0,0) beginnt und bei (100,50) endet. Zum Setzen dieses Stils wird dann die Methode setPaint verwendet. //Festlegung der Art des Füllmusters GradientPaint p = new GradientPaint (0f, 0f, Color.black, 100f, 50f, Color.White); screen2D.setPaint(p); Nachdem Linieneigenschaften und Füllstil festgelegt wurden, kann das Zeichnen von Objekten in Java2D beginnen. Objekte in Java2D werden als geometrische Formen angelegt, die im Package java.awt.geom enthalten sind. Im Gegensatz zum bisherigen Ansatz unterscheidet man in Java2D zwischen der Definition der Objekte und deren Zeichnen oder Füllen. Als Beispiel kann die oben definierte Methode drawrect betrachtet werden. In Java sind hier zwei Abläufe zusammengefasst: Die Definition und das Malen eines Quadrats. In Java2D hingegen muss ein Quadrat in einer eigenen Anweisung definiert werden und dann anschließend als Argument an die Methoden draw oder fill übergeben werden, die das Zeichnen regeln. Zur Definition grafischer Primitive stehen die folgenden Methoden zur Verfügung: Lines2D.Float zur Definition von Linien. Diese Klasse erwartet vier Argumente, die vom Typ float sein müssen: Die x- und y-Koordinate des Anfangspunkts gefolgt von den Koordinaten des Endpunkts der Linie. Im folgenden Beispiel wird eine Linie gezeichnet, die von (0,0) bis (100,100) geht. Es sei nochmals darauf hingewiesen, dass der Datentyp der Argumente float ist. Sollen hier keine Variablen, sondern einfache Zahlen verwendet werden, so kann ein F an die Zahlen angehängt werden, wodurch dem Compiler mitgeteilt wird, dass das jeweilige Argument kein Integer-Wert ist. //Zeichnen einer Linie //Definitionsphase der Linie Linie2D.Float l = new Line2D.Float (0F, 0F, 100F, 100F); //Anzeigen der Linie screen2D.draw(l); Rectangle2D.Float zur Definition von Rechtecken. Rectangle2D.Float erwartet 4 Argumente: Die Koordinaten der linken oberen Ecke des Rechtecks und seine Breite und Höhe. Auch diese Werte müssen vom Typ float sein. An dieser Stelle sollte darauf hingewiesen werden, dass die Definition der Primitive, die bereits im Kontext von Java betrachtet wurde, stets bezüglich der Argumente mit den Definitionen in Java2D übereinstimmt. Rectangle2D.Float verwendet daher dieselben Argumente wie drawRect oder fillRect. RoundRectangle2D.Float zur Definition von Rechtecken mit abgerundeten Ekken. RoundRectangle2D.Float wird ebenso wie Rectangle2D.Float aufgerufen. Ellipse2D.Float zur Definition von Ellipsen. Ovale werden in Java2D als Ellipsen bezeichnet und mit der Klasse Ellipse2D.Float erzeugt, die dieselben Argumente wie Rectangle2D.Float erwartet. Arc2D.Float zur Definition von Kreisausschnitten. Auch die Definition von Kreisausschnitten erfolgt in Java2D genau gleich wie in Java. Anstelle von sechs Parametern werden hier aber sieben verwendet: Die ersten vier stimmen mit denen der Ellipse überein, die nächsten beiden betreffen Winkelstartpunkt und Winkellänge und der siebte gibt an, wie der Kreisausschnitt geschlossen werden soll. Die verschiedenen Werte, die der siebte Parameter annehmen kann, sind in Abb. 4-10 dargestellt. Abb. 4.10: Kreisbogenabschluss in Java2D Im folgenden Beispiel wird ein Kreisbogenausschnitt erzeugt, der mit einem geraden Abschluss geschlossen wird. Der Kreis ist durch die Koordinaten (100,100), die Breite 30 und die Höhe 30 bestimmt, der Kreisausschnitt durch die Winkelangabe 90 Grad und die Winkellänge 45 Grad. //Zeichnen eines Kreisausschnitts //Definitionsphase des Kreisausschnitts Arc2D.Float a= new Arc2D.Float (100F,100F,30F,30F,90F,45F,Arc2D.CHORD); //Anzeigen des Kreisausschnitts screen2D.draw(a); Polygone Die Verarbeitung von Polygonen in Java2D unterscheidet sich von der bisher betrachteten, da neben der Möglichkeit, ein Polygon zu definieren (eine Liste von Punkten mittels moveTo und lineTo abzulegen), in Java2D lediglich die einzelnen Punkte eines Polygons angegeben werden können. Die Verbindung der Punkte kann dann als gerade Linie, als quadratische Kurve oder als Bezier-Spline erfolgen. Abb. 4-11 illustriert die Möglichkeiten, Punkte eines Polygons zu verbinden. Abb. 4.11: Punktverbindungen in Java2D Zunächst muss ein GeneralPath-Objekt angelegt werden, das den Pfad von Punkt zu Punkt verwaltet. Hierzu ist die folgende Syntax zu verwenden: GeneralPath <Name des Pfades> = new generalPath(); Nachdem ein leeres Polygon angelegt wurde, wird der erste Punkt des Linienzugs mit der moveTo-Methode (Argumente: Punktkoordinaten x und y) in das Polygon eingefügt. Alle weiteren Punkte werden mit der Methode lineTo, die dieselben Argumente wie die moveTo-Methode erwartet, in das Polygon eingefügt. Sowohl moveTo als auch lineTo erwarten alle Argumente als float-Werte. Nachdem das Polygon angelegt wurde, kann der Linienzug mit der Methode closePath geschlossen werden. Das folgende Beispiel illustriert die Verwendung der verschiedenen Methoden. //Erzeugen eines Polygons aus 5 Punkten und Schliessen des Linienzugs GeneralPath p = new generalPath(); p.moveTo(0F,0F); p.lineTo(0F, 10F); p.lineTo(30F, 30F); p.closePath(); //Fuellen des Objekts screen2D.fill(p); Transformationen Das Transformationsattribut des Graphics2D-Kontexts, das als eine Instanz der Klasse AffineTransform definiert ist, kann verändert werden, um Verschiebungen, Drehungen, Skalierungen oder Verzerrungsoperationen grafischer Objekte durchzuführen. Verzerrungsoperationen zeichnen sich dadurch aus, dass eine Gruppe von Linien auch nach Anwendung der anderen Transformationen noch parallel ist. Graphics2D bietet hierzu verschiedene Möglichkeiten an, die durch eine Generierung einer neuen Instanz der Klasse AffineTransform oder durch den Wechsel des Attributs mittels der Methode setTransform charakterisiert sind. Die Klasse AffineTransform definiert die folgenden bereits vorgefertigten Methoden: getRotateInstance zur Rotation, getScaleInstance zur Skalierung, getShearInstance für Verzerrungsoperationen, getTranslateInstance zur Verschiebung. Alternativ kann auch eine der Transformationsmethoden verwendet werden, die Teil von Graphics2D sind. Wird eine dieser Methoden aufgerufen, so wird die resultierende Transformation an die momentan verwendete angehängt und das neue Resultat angezeigt. Hierzu stehen die folgenden Methoden zur Verfügung: rotate zur Angabe eines Rotationswinkels in Grad. scale zur Angabe eines Skalierungsfaktors in x- und in y-Richtung. shear zur Angabe eines Verzerrungsfaktors in x- und in y-Richtung. translate zur Angabe der Verschiebung in x- und in y-Richtung. Eine AffineTransform kann erzeugt und mit anderen Transformationsmethoden verbunden werden. Eine andere Möglichkeit ist das Überladen der drawImage-Methode, um den Typ der AffineTransform anzugeben, der angewendet werden soll. Spezifiziert man eine Transformation, wenn drawImage aufgerufen wird, so wird das Transformationsattribut des Graphics2D-Kontexts nicht verändert. Das folgende Beispiel illustriert die Verwendung der Transformationsmethoden. AffineTransform at = new AffineTransform(); // Die folgenden Zeilen sind alternativ // Keine Verschiebung at.setToIdentity(); // Verschiebung um die Haelfte der Breite w und der Hoehe h at.translate(w/2, h/2); // ... oder Rotation um den Winkel 45 Grad at.rotate(Math.toRadians(45)); // ... oder Skalierung um 50 Prozent in x- und y-Richtung at.scale(0.5, 0.5); // Erzeuge neue Transformation und Daten aus at uebernehmen AffineTransform at2 = new AffineTransform(); at2.concatenate(at); // Groesse verdoppeln at2.scale(2, 2); // Transformation anwenden screen2.setTransform(at2); Anzeigebegrenzung Jede grafische Form, die mit Java2D erzeugt wird, kann zur Begrenzung der anzuzeigenden Fläche verwendet werden. Definiert man bspw. einen Kreis und positioniert diesen in der Mitte der Anzeigefläche, so wird nur der Teil einer Grafik oder eines Bildes angezeigt, der sich im Bereich der Fläche des Kreises befindet. Dies wird auch als Clipping bezeichnet. Als Teil des Graphics2D-Kontexts kann das Clip-Attribut gesetzt werden, indem die Methode Graphics2D.setClip aufgerufen wird und dieser die Form als Argument übergeben wird, die den anzuzeigenden Bereich festlegt. Der Clipping-Bereich kann anschließend noch verkleinert werden, indem man der clip-Methode eine weitere Fläche übergibt. Es wird dann nur der Bereich angezeigt, in dem sich der aktuelle Clip und die neue Form überschneiden. Im folgenden Beispiel werden zwei Rechtecke verwendet, die sich teilweise überschneiden. Der Clip betrifft dann nur den Bereich, in dem die Rechtecke überlappen. Rectangle2D r1 = new Rectangle2D.Float(0F, 0F, 100F, 200F); Rectangle2D r2 = new Rectangle2D.Float(20F, 20F, 200F, 100F); // Setzen des Ausschnittbereichs screen2.setClip(r1); // Definition eines Ueberlappungsbereichs screen.clip(r2); Vordergrundobjekte Java2D bietet die Möglichkeit, Objekte so anzuzeigen, dass sie andere verdecken. Die Verdeckung ist aber nur eine Art und Weise, Objekte in einem Verbund anzuordnen. Die dazu einsetzbare AlphaComposite-Klasse kapselt verschiedene Stile, die bestimmen, wie überlappende Objekte am Bildschirm angezeigt werden. Eine AlphaComposite-Instanz kann weiterhin mittels eines alpha-Werts den Grad der Transparenz angeben. Hierbei ist ein Objekt vollkommen sichtbar, wenn alpha auf 1.0 steht und vollkommen transparent, wenn alpha den Wert 0.0 hat. Der alpha-Wert kann weiterhin auf einen beliebigen Wert zwischen 0.0 und 1.0 eingestellt werden. In Tab. 4-2 sind die Stile, die zur Definition der Überlappungseigenschaften eingesetzt werden können, angegeben.

Stil | Bedeutung
Source-over (SRC_OVER) | Wenn Bildpunkte im anzuzeigenden Objekt (Source) dieselben Koordinaten haben wie vorher angezeigte (Destination), werden die Source-Pixel über den Destination-Pixeln angezeigt.
Source-in (SRC_IN) | Überlappen Source- und Destination-Pixel, so werden nur die Source-Pixel angezeigt.
Source-out (SRC_OUT) | Überlappen Source- und Destination-Pixel, so werden nur die Source-Pixel außerhalb des Überlappungsbereichs dargestellt. Die Pixel des Überlappungsbereichs werden gelöscht.
Destination-over (DST_OVER) | Überlappen Source- und Destination-Pixel, so werden nur die Source-Pixel außerhalb des Überlappungsbereichs dargestellt. Die Pixel des Überlappungsbereichs bleiben unverändert.
Destination-in (DST_IN) | Überlappen Source- und Destination-Pixel, so wird der Alpha-Faktor der Source auf die Destination-Pixel angewendet. Wenn Alpha gleich 1.0 ist, so bleiben die Pixel des Überlappungsbereichs unverändert. Ist Alpha gleich 0.0, so werden die Pixel des Überlappungsbereichs gelöscht.
Destination-out (DST_OUT) | Überlappen Source- und Destination-Pixel, so wird der Alpha-Faktor der Source auf die Destination-Pixel angewendet. Wenn Alpha gleich 1.0 ist, so werden die Pixel des Überlappungsbereichs gelöscht. Ist Alpha gleich 0.0, so bleiben die Pixel des Überlappungsbereichs unverändert.
Clear (CLEAR) | Überlappen Source- und Destination-Pixel, so werden die Pixel dieses Bereichs gelöscht.

Tab. 4.2: Überlappungsdefinitionen in Java2D Um den in Graphics2D verwendeten Überlappungsstil zu verändern, muss ein AlphaComposite-Objekt erzeugt werden und an die setComposite-Methode übergeben werden. Im folgenden Beispiel wird ein neues AlphaComposite-Objekt aco angelegt, indem AlphaComposite.getInstance aufgerufen wird und der entsprechende Stil gesetzt wird. Soll ein anderer Überlappungsstil oder alpha-Wert gesetzt werden, so wird AlphaComposite.getInstance nochmals aufgerufen und aco das neue AlphaComposite zugewiesen. alpha wird als zweiter Parameter an AlphaComposite.getInstance übergeben. AlphaComposite aco = AlphaComposite.getInstance(AlphaComposite.SRC); float alpha = 0.5; aco = AlphaComposite.getInstance(AlphaComposite.DST_OUT, alpha); screen2.setComposite(aco); Abbildungsqualität Einer der vielen Vorteile von Java2D ist die skalierbare Darstellungsqualität. In einem Graphics2D-Kontext kann mit einem Attribut angegeben werden, ob Objekte so schnell wie möglich oder aber in einer größtmöglichen Qualität dargestellt werden sollen. Zum Setzen der Darstellungseigenschaften muss ein RenderingHints-Objekt generiert werden und an die Methode setRenderingHints des Graphics2D-Kontexts übergeben werden. Wenn lediglich ein Attribut gesetzt werden soll, kann die Methode setRenderingHint aufgerufen werden, indem ein Paar aus Schlüsselwort und Wert als Argument der zu setzenden Eigenschaft übergeben wird. Die hierzu notwendigen Paare aus Schlüsselwort und Wert sind in der Klasse RenderingHints definiert. Ein Beispiel, in dem die Eigenschaft rendering (Anzeigeart) auf den Wert quality gesetzt wird, ist im Folgenden angegeben. Es muss aber beachtet werden, dass eine Veränderung des Darstellungsmodus nicht von allen Plattformen umgesetzt werden kann. Obwohl daher dieser Wert stets gesetzt werden kann, ist eine Umsetzung nicht in allen Fällen gewährleistet. screen2.setRenderingHint(RenderingHints.KEY_RENDERING,qu ality); In der Klasse RenderingHints werden die folgenden Eigenschaften unterstützt (ein Setzen auf den Wert default bewirkt, dass die standardmäßig auf einer Plattform vorhandenen Anzeigemöglichkeiten verwendet werden): Alpha-Interpolation (Werte default, quality oder speed). Antialiasing (Werte default, on oder off). Color Rendering (Farbdarstellung), (Werte default, quality oder speed). Dithering (Anzahl verwendeter Farben), (Werte default, disable oder enable). Fractional Metrics (Werte default, on oder off). Interpolation (Werte nearest-neighbor, bilinear oder bicubic). Rendering (Anzeige), (Werte default, quality oder speed). Text Antialiasing (Werte default, on oder off). Komplexe Grafikformen Mittels geometrischer Formen können durch Überlagerung komplexe Flächen geschaffen werden. Die Disziplin, die sich mit der Erzeugung geometrischer Formen durch Anwendung boole'scher Operationen beschäftigt, wird auch als Constructive Area Geometry (CAG) bezeichnet. In Java2D stehen für derartige Aufgaben sog. Areas (Flächen) zur Verfügung. Eine Area kann aus einer beliebigen geometrischen Form erzeugt werden und unterstützt die folgenden boole'schen Operationen: die Vereinigungsmenge (Methode union), die Schnittmenge (Methode intersection), die Subtraktion (Methode subtract) und das exklusive Oder (XOR) (Methode xor). Beispiele für derartige Mengen sind in Abb. 4-12 angegeben. Im folgenden Code-Beispiel wird erläutert, wie eine Schnittmenge zweier Kreise generiert werden kann. Die Verwendung der anderen Methoden erfolgt analog. Abb. 4.12: Boole'sche Operationen mit Flächen // Erzeuge Kreis kreis= new Ellipse2D.Float(); // Erzeuge erste Area fuer Kreis kreis1 = new Area(kreis); // Analog fuer Kreis2 kreis2 = new Area(kreis); // Setze Koordinaten des Kreises kreis.setFrame(0F, 0F, 30.0, 30.0); // Erzeuge Area fuer Kreis1 kreis1 = new Area(kreis); //Analog fuer Kreis2 kreis.setFrame(20F, 0F, 30.0, 30.0); kreis2 = new Area(kreis); //Schnittmenge kreis1.intersect(kreis2); //Anzeigeuebergabe screen2.fill(kreis1); Bilder in Benutzeroberflächen Zur Verwendung von Bildern steht in Java die Klasse Image zur Verfügung, die Teil des Packages java.awt ist. Zum Laden und Anzeigen von Bildern werden die Methoden der Klassen Graphics und Applet eingesetzt, wenn Applets implementiert werden. Die hierzu notwendigen Mechanismen werden in diesem Unterkapitel beschrieben. Um ein Bild anzeigen zu können, muss dieses zunächst über das World Wide Web in ein Java-Programm eingeladen werden. Es ist daher auch verständlich, dass Bilder separat von Java-Klassendateien gespeichert werden. Die in Java verwendeten Dateiformate sind JPEG (Endung .jpg) oder GIF (Endung .gif) [Stei98]. Zum Laden eines Bildes über das WWW muss eine URL angegeben werden. Hierzu wird die Klasse URL verwendet, die Teil des Packages java.net ist. Mit der folgenden Anweisung kann ein URL-Objekt erzeugt werden, das sich auf das Bild kom.jpg bezieht: URL url = new URL("http://www.kom.e-technik.tu-darmstadt.de/kom.jpg"); Innerhalb der Applet-Klasse kann ein Bild auf die folgenden zwei Arten geladen werden: mit der getImage-Methode, die als einziges Argument ein URL-Objekt erwartet, das das zu ladende Bild angibt. mit der getImage-Methode, die zwei Argumente akzeptiert: Eine Basis-URL und einen String, der den relativen Pfadnamen oder den Bildnamen angibt, an der das Bild zu finden ist. Hierbei spezifiziert die URL zusammen mit dem String den gesamten Namen des Bildes. Oftmals ist es unflexibel, eine URL fest im Programm zu verankern. Es existieren daher zwei weitere Mechanismen, die auf die Verwendung einer URL verzichten: die Methode getDocumentBase. Diese Methode erzeugt ein URL-Objekt, das das Verzeichnis repräsentiert, in der die Webseite enthalten ist, die das Applet aufruft. die Methode getCodeBase, die ein URL-Objekt erzeugt, das das Verzeichnis repräsentiert, in dem die Datei zu finden ist, in der die Hauptklassen des Applets enthalten sind. Die Entscheidung, ob entweder getDocumentBase oder getCodeBase verwendet wird, hängt davon ab, ob zu ladende Bilder sich in Unterverzeichnissen des Applets oder in Unterverzeichnissen der Webseite befinden, aus denen das Applet gestartet wird. Sind Bilder in Java-Archiven (JARs) enthalten, so werden diese automatisch extrahiert, wenn das Bild geladen werden soll. Nach dem Laden eines Bildes kann es mittels der Methode drawImage, die Teil der Klasse Graphics ist, angezeigt werden. Hierzu sind die folgenden Parameter zu verwenden: das Image-Objekt, das angezeigt werden soll, die x- und y-Koordinaten, an denen das Bild darzustellen ist und das Schlüsselwort this. Die Koordinaten, an denen ein Bild dargestellt wird, werden in ähnlicher Art und Weise verwendet, wie bei der Erzeugung von Rechtecken mit der Methode draw-Rect. Hierbei geben die Koordinaten die linke obere Ecke des darzustellenden Bildes an. Optional kann auch die Größe eines Bildes angegeben werden. In diesem Fall wird das Bild dementsprechend verkleinert oder vergrößert, wenn die Größenangaben nicht der tatsächlichen Größe des Bildes entsprechen. Zur Abfrage der tatsächlichen Größe des Bildes können die Methoden getWidth() und getHeight() verwendet werden. drawImage wird dann mit den folgenden Parametern aufgerufen: dem Image-Objekt, das angezeigt werden soll, den x- und y-Koordinaten, an denen das Bild darzustellen ist, der Breite und Höhe des Bildes in Pixeln und dem Schlüsselwort this. Der jeweils letzte Parameter, mit dem drawImage aufgerufen wird, ist das Schlüsselwort this, das als Referenz innerhalb eines Objekts auf sich selbst eingesetzt wird. Im Kontext von drawImage wird this verwendet, damit das Applet den Ladevorgang eines Bildes über das WWW überwachen kann. Hierzu wird das Interface ImageObserver eingesetzt. Klassen, die dieses Interface implementieren, können den Fortschritt des Ladevorgangs beobachten und Meldungen erzeugen, die dem Anwender mitteilen, dass der Ladevorgang noch abläuft. Das folgende Applet lädt das Bild kom.jpg und stellt es um die Hälfte vergrößert dar. Abb. 4.13: Ausgabe des Bild-Applets Das eigentliche Laden erfolgt in der Methode init. In der Methode paint wird die Größe des Bildes abgefragt und diese dann um die Hälfte vergrößert. Die Ausgabe des Applets ist in Abb. 4-13 dargestellt. import java.applet.Applet; import java.awt.Graphics; import java.awt.Image; public class BildAnzeige extends Applet { Image img; public void init() { img = getImage(getCodeBase(), "kom.jpg"); } public void paint(Graphics g) { int breite = img.getWidth(this); int hoehe = img.getHeight(this); g.drawImage(img, 20, 20, (3*hoehe/2), (3*breite/2), this); } } Animation in Benutzeroberflächen Animationen können in Benutzeroberflächen auf vielfältige Art und Weise verwendet werden bspw., um Textfarben dynamisch zu verändern, um einen Film durch aneinandergereihte Einzelbilder (wobei diese Bilder in einem Array-Objekt gespeichert werden) zu simulieren, aber auch, indem Grafikobjekte ihre Position dynamisch verändern, wodurch wiederum der Eindruck eines ablaufenden Films entsteht. Hierbei tritt allerdings ein großes Problem auf: Die Darstellung derartiger Animationen flackert, wenn keine Ergänzungen des Codes vorgenommen werden. In diesem Abschnitt werden Mechanismen beschrieben, die das Flackern verhindern, speziell die Doppelpufferungsmethode. Die Ursache für Flackereffekte liegt darin, dass zur Anzeige eines neuen Bildes einer Animation die Methode repaint aufgerufen werden muss, die wiederum paint aufruft. repaint ruft allerdings paint nicht direkt auf, sondern vorher die Methode update, die den Inhalt des Bildschirms dadurch löscht, dass der Bildschirm mit der Hintergrundfarbe aufgefüllt wird. Es ist nun klar ersichtlich, dass ein Flimmereffekt kaum vermeidbar ist, wenn zwischen je zwei Bildern einer Animation ein anderes (einfarbiges) Bild eingeschoben wird. Grundsätzlich stehen die folgenden zwei Möglichkeiten zur Verfügung, um diesen Effekt zu verhindern: Überschreiben der update-Methode, so dass der Bildschirm nicht mehr gelöscht wird. Überschreiben der Methoden update und paint und Verwendung der Doppelpufferungstechnik. Verhinderung des Bildschirmlöschens Standardmäßig sieht die update-Methode folgendermaßen aus: public void update (Graphics screen){ screen.setColor(getBackground()); screen.fillRect(0,0,getSize().width, getSize().height); screen.setColor(getForeground()); paint(screen); } Mittels der Methode getSize wird hierbei die Größe des Bildschirmbereichs des Applets in Erfahrung gebracht. Eine Möglichkeit besteht nun darin, das Löschen des Bildschirms auszulassen. Dies ist allerdings nur dann sinnvoll, wenn sich kein Objekt bewegt. Anderenfalls sind Darstellungsfehler zu erwarten. Die überschriebene Methode ist im Folgenden angegeben. Es muss allerdings darauf hingewiesen werden, dass dieser Ansatz zwar einfach, aber problematisch ist. Es ist daher ratsam, die im Folgenden beschriebene Doppelpufferung zu verwenden. public void update (Graphics screen){ paint(screen); } Doppelpufferung Bei der Doppelpufferung wird der gesamte anzuzeigende Inhalt in einen nicht sichtbaren Bereich, einen Puffer, geschrieben, der anschließend in den sichtbaren Bildschirmbereich kopiert wird. Der Vorteil der Doppelpufferung besteht vor allem darin, dass die Berechnung des anzuzeigenden Inhalts von der Anzeige entkoppelt wird. Zustände, in denen das Bild erst teilweise berechnet ist, werden daher dem Betrachter nicht präsentiert. Hierdurch ergibt sich speziell bei Animationen ein flüssiger Bewegungsablauf. Die Doppelpufferung ist allerdings mit wesentlichem Aufwand verbunden, da zwei Bildschirmbereiche verwendet werden müssen. Hierdurch wird mehr Rechenaufwand und auch mehr Speicherplatz im Hauptspeicher benötigt. Vorteil der Doppelpufferung ist, dass nahezu sämtliche Flackereffekte ausgeschlossen werden können. Zur Verwendung der Doppelpufferung sind die folgenden vier Schritte notwendig: Definition von Image- und Graphics-Objekten, die im Verborgenen arbeiten. Zur Übergabe an die paint-Methode müssen dies Instanzvariablen sein. Image verborgenesBild; Graphics verborgen; Während der Initialisierung eines Applets müssen diese beiden Objekte initialisiert werden: verborgenesBild = createImage(getSize().width, getSize().height); verborgen = verborgenesBild.getGraphics(); Alle Ausgaberoutinen müssen nun mit dem im Verborgenen arbeitenden Graphics-Objekt erfolgen und nicht länger mit dem Graphics-Objekt, das mit der Anzeige assoziiert ist: verborgen.drawRect(0,0,100,100); Ist ein Bild vollständig erstellt, so wird es in den sichtbaren Bereich kopiert. Es darf auch nicht vergessen werden, die update-Methode derart zu überschreiben, dass die Anweisung zum Löschen des Bildschirms entfernt wird. //.... screen.drawImage(verborgenesBild, 0, 0, this); //...... public void update (Graphics screen){ paint(screen); } Im Folgenden ist ein Beispiel angegeben, das die Technik der Doppelpufferung anwendet. Hierzu soll ein roter Kreis jeweils von links nach rechts und dann zurück über den Bildschirm bewegt werden. Die Geschwindigkeit soll hoch genug sein, um den Eindruck einer fortlaufenden Animation zu erzeugen. Hierzu wird eine Variable xpos verwendet, die von Bild zu Bild erhöht bzw. verringert wird. Um das Applet letztendlich zu beenden, wird die Methode destroy verwendet, die den Speicherbereich des im Verborgenen arbeitenden Bildschirmbereichs freigibt. Wie aufwendig die Doppelpufferung tatsächlich ist, wird erkennbar, wenn man den Bildschirmbereich des Applets mit der Maus vergrößert. In diesem Fall läuft das Applet sehr viel langsamer und auch ruckartig. Die Ausgabe des Applets ist in Abb. 4-14 dargestellt. import java.applet.Applet; import java.awt.Graphics; import java.awt.Image; public class Kugeln extends Applet implementsRunnable{ Thread t; Image verborgenesBild; Graphics verborgen; int xpos = 0; int xBew = 10; public void init() { verborgenesBild = createImage(getSize().width, getSize().height); verborgen = verborgenesBild.getGraphics(); } public void start() { if (t == null) { t = new Thread(this); t.start(); } } public void stop() { t = null; } public void run() { Thread aktThread = Thread.currentThread(); while (t == aktThread) { xpos += xBew; // noch im Bildschirmbereich ?? if ((xpos > getSize().width) | (xpos < 0)) xBew *= -1; repaint(); try { Thread.sleep(100); } catch (InterruptedException e) {} } } public void update(Graphics screen) { paint(screen); } public void paint(Graphics g) { verborgen.setColor(Color.white); verborgen.fillRect(0,0,getSize().width, getSize().height); verborgen.setColor(Color.red); verborgen.fillOval(xPos, 10, 100, 100); //Wechsel zum sichtbaren Bereich g.drawImage(verborgenesBild, 0, 0, this); } public void destroy() { verborgen.dispose(); } } Abb. 4.14 : Ausgabe des Kugel-Applets Audio in Benutzeroberflächen Ab Java 1.2 stehen eine Reihe von Möglichkeiten zur Verfügung, verschiedene Typen von Audiodateien aus Applets oder Applications heraus abzuspielen. Hierzu zählen die Dateiformate AIFF, AU, WAV, MIDI (Typ 0), MIDI (Typ 1) und RMF. Es können Audiodaten in Mono oder Stereo verarbeitet werden, die mit 8 oder 16 bit aufgenommen wurden, wobei Abtastraten zwischen 8 kHz und 48 kHz verwendet werden. Das Abspielen von Tondateien aus einem Applet heraus wurde in Java 1.2 gegenüber früheren Versionen nicht verändert. Um eine derartige Datei abzuspielen, lädt man einen Audio-Clip mit dem Befehl Applet.getAudioClip und kontrolliert die Wiedergabe mit den Methoden play, loop und stop, die Teil der Klasse AudioClip sind. Um bspw. eine Datei abzuspielen, die im Format WAV gespeichert ist, sind die im Folgenden aufgezählten Schritte nötig. WAV ist ein insbesondere auf PCs häufig verwendetes Audioformat. Aufruf von Applet.getAudioClip mit einer URL als Parameter, der spezifiziert, wo die WAV-Datei zu finden ist. Hierbei können entweder nur ein Parameter (die URL) oder auch zwei Parameter (URL und Verzeichnisname der Audiodatei) angegeben werden. Soll der aktuelle Pfad, an dem das Applet ausgeführt wird, verwendet werden, so muss der Befehl getCodeBase() spezifiziert werden. AudioClip clip = getAudioClip(getCodeBase(),"beispiel.wav"); Aufruf von play oder loop (Endlosschleife). Audiodaten werden dann geladen, wenn ein Objekt des Typs AudioClip angelegt wird, nicht erst beim Abspielen einer derartigen Datei. Zum Abspielen der unterstützten Audioformate kann auch der Befehl Applet.play ohne vorherige Ausführung von Applet.getAudioClip verwendet werden, bei dessen Benutzung aber die Daten nicht vorab geladen werden. Dies bewirkt, dass einige wichtige Eigenschaften eines Applets, wie die Zeichengeschwindigkeit oder auch die Event-Verarbeitung, signifikant verlangsamt werden, wenn der Benutzer eine Audiodatei zum ersten Mal verwendet. Das folgende Beispiel ist eine Anwendung für das Abspielen einer WAV-Audiodatei in Java. Unabhängig vom Typ einer Audiodatei ist der Lade- und Abspielvorgang immer derselbe. import java.awt.Graphics; import java.applet.AudioClip; public class AudioPlayer extends java.applet.Applet implements Runnable { AudioClip beispielClip; Thread t; public void start () { if (t == null) { t = new Thread(this); t.start(); } } public void stop () { if (t != null) { if (beispielClip != null) beispielClip.stop(); t = null; } } public void init() { beispielClip = getAudioClip(getCodeBase(), "beispiel.wav"); } public void run() { if (beispielClip != null) beispielClip.loop(); Thread dieserThread = Thread.currentThread(); while (t == dieserThread) { try { Thread.sleep(1000); } catch (InterruptedException e) {} } } } Um die Wartezeit des Benutzers zu verringern, wird die Audiodatei in einem Hintergrund-Thread geladen, anstatt in der init-Methode des Applets. Wenn der Benutzer die Klangwiedergabe startet, bevor die Datei vollständig geladen wurde, kann das Applet so geeignet reagieren. Ab JDK 1.2 können sowohl Applications als auch Applets Audiodateien abspielen. Hierzu wurde dem Package java.applet.Applet eine neue, als static gekennzeichnete Methode hinzugefügt, die es Applications erlaubt, Audio-Clips aus einer URL zu generieren: public static final AudioClip newAudioClip(URL r) Um nun eine Audiodatei eines Applets abzuspielen, wird die Methode Applet.new-AudioClip verwendet, um die Datei zu laden. Anschließend können wiederum die Methoden play, loop, und stop in bereits bekannter Art und Weise eingesetzt werden. Die folgenden Schritte verdeutlichen den Vorgang: Aufruf von Applet.newAudioClip und Übergabe der URL, an der die Audiodatei gespeichert ist. Aufruf von play oder loop im Audio-Clip. Der Audio-Player des vorherigen Beispiels kann sehr leicht als Application umgeschrieben werden. Hauptsächlicher Unterschied ist, dass nun Applet.newAudioClip verwendet wird, um Audiodateien zu laden. import java.applet.AudioClip; public class AudioPlayer implements Runnable { AudioClip beispielClip; Thread t; public void start () { if (t == null) { t = new Thread(this); t.start(); } } public void stop () { if (t != null) { if (beispielClip != null) beispielClip.stop(); t = null; } } public void init() { beispielClip = newAudioClip("beispiel.wav"); } public void run() { if (beispielClip != null) beispielClip.loop(); Thread dieserThread = Thread.currentThread(); while (t == dieserThread) { try { Thread.sleep(1000); } catch (InterruptedException e) {} } } public static void main (String[] args) { AudioPlayer a = new AudioPlayer(); a.start(); } } Die Audioverarbeitung in Java zeichnet sich durch einen einheitlichen Zugriff auf die Audiomöglichkeiten der zugrunde liegenden Plattform aus. Hierdurch wird es Java-Programmen möglich, Audiodaten aufzunehmen (auch diese zu synthetisieren) und sie wieder abzuspielen. Dienste auf höherer Ebene, wie bspw. die Kompression oder Dekompression, die Synchronisation, das Streaming, Container-Zugriffe (Lesen oder Schreiben) und Netzwerktransporte, werden vom Java Media Framework (JMF) realisiert. JMF ermöglicht es Java-Programmen in einer einheitlichen und einfachen Art und Weise, zeitbasierte Daten, wie bspw. Audio und Video, zu synchronisieren und diese anzuzeigen. Die JMF-Implementierung der Firma Sun verwendet die Java-Sound-Engine, um Audiodaten abzuspielen. Informationen über JMF sind unter der URL http://java.sun.com/products/java-media zu finden.