Geometrie I: vordefinierte Klassen



XML und Java ... Die Java 2D API ... Next: Geometrie II: benutzerdefinierte Umrisse

[nach oben]


Einleitung

Das Paket java.awt.geom stellt fertige geometrische Objekte zur Verfügung, die in ein Graphics2D-Objekt gezeichnet werden können. Die wichtigsten dieser Objekte möchte in diesem Kapitel vorstellen.

Erst im nächsten Kapitel werde ich darstellen, wieso es eigentlich möglich ist, verschiedene geometrische Objekte in ein Graphics2D-Objekt zu zeichnen, obwohl dieses gar nichts über Geometrie "weiß". Wer eigene geometrische Objekte definieren möchte, sei ebenfalls auf das nächste Kapitel verwiesen.

Die folgende Grafik zeigt die Vererbungshierarchie für die geometrischen Objekte:

Auf das Shape-Interface, daß alle Objekte dieser Hierarchie implementieren, gehe ich im nächsten Kapitel genauer ein.

[nach oben]


Punkte

Punkte sind in der Java 2D API hauptsächlich dazu da, um Koordinaten für komplexere geometrische Objekte wie z.B. Rechtecke bereitzustellen. Ein Punkt hat im mathematischen Sinne keine Ausdehnung und diese Vorstellung wurde auch hier übernommen. Deshalb ist ein Punkt nicht mit einem Pixel zu verwechseln.

Punkte werden in der Java 2D API durch die abstrakte Klasse java.awt.geom.Point2D repräsentiert. Point2D besitzt zwei innere Klassen, die sich nur in der Art der Speicherung der Koordinaten unterscheiden. Bei diesen Klassen handelt es sich um Point2D.Double und Point2D.Float. Nur von diesen Klassen können Instanzen erstellt werden.

Dieses Konzept der inneren Klassen zur Realisierung unterschiedlicher Speicherungsformate für Koordinaten (meist float, double) wird in der Java 2D API sehr häufig benutzt. Hierbei gibt es immer eine Menge von Methoden aus einer abstrakten Superklasse, die für beide innere Klassen verwendet werden und zwei Mengen von Methoden, die jeweils in den inneren Klassen definiert sind. Diese Methoden dienen typischerweise zu speicherungsformatabhängigen set-/get-Operationen (z.B. setLocation(double x, double y) für Point2D.Double und setLocation(float x, float y) für Point2D.Float.

Die folgende Grafik zeigt die Vererbungshierarchie der Point2D-Klassen, das Schema der inneren Klassen trifft auch für die restlichen Geometrieklassen zu.

Die folgende Tabelle enthält einige wichtige Methoden von Point2D.Double. Die Methoden von Point2D.Float sind sinngemäß identisch, bis auf das sie float als Datentyp benutzen. double bietet eine höhere Präzision. Für die vollständige Methodenliste beider Klassen verweise ich auf die Java API Spezifikation.

Methode Beschreibung
Point2D.Double() konstruiert einen neuen Punkt mit den Koordinaten (0,0)
Point2D.Double(double x, double y) konstruiert einen neuen Punkt mit den Koordinaten (x,y)
double getX() liefert die X-Koordinate des Punktes
double getY() liefert die Y-Koordinate des Punktes
void setLocation(double x, double y) setzt die X-und Y-Koordinate des Punktes

In Point2D selbst sind bereits Methoden definiert, die sowohl von Point2D.Double als auch von Point2D.Float verwendet bzw. überschrieben werden, von denen ich hier einige wichtige vorstellen möchte:

Methode Beschreibung
double distance(double PX, double PY) gibt die Distanz zum Punkt (PX,PY) zurück.
double distanceSq(double PX, double PY) gibt das Quadrat der Distanz zum Punkt (PX,PY) zurück. (nützlich z.B. für den Satz des Pythagoras)
double distance(Point2D pt) gibt die Distanz zum Punkt pt zurück.
double distanceSq(Point2D pt) gibt das Quadrat der Distanz zum Punkt pt zurück.
double getX() liefert die X-Koordinate des Punktes in doppelter Präzision
double getY() liefert die Y-Koordinate des Punktes in doppelter Präzision

Das folgende Programmbeispiel die Entfernungsmessung zwischen zwei Punkten p1 und p2 (Zugriff auf die Quelle):


import java.awt.geom.*;

public class MyPoint2D {
	public static void main(String[] args) {
		//Punkt erstellen
		Point2D p1 = new Point2D.Float( 10f,10f );
		Point2D p2 = new Point2D.Float( 0f,0f );

		//Punktkoordinaten ausgeben
		System.out.println( "P1: "+p1 );
		System.out.println( "P1: "+p2 );
		
		//Distanz ausgeben
		System.out.println( "Strecke P1P2: "+p1.distance(p2) );
	}
}

[nach oben]


Linien

Linien werden in der Java 2D API durch die abstrakte Klasse java.awt.geom.Line2D repräsentiert. Analog zu Point2D besitzt auch Line2D zwei innere Klassen. Dieses Muster wird sich auch bei allen noch nicht vorgestellten Geometrieklassen wiederholen. Die inneren Klassen von Line2D heißen Line2D.Double und Line2D.Float.

Die folgende Tabelle enthält einige wichtige Methoden von Line2D.Double. Die Methoden von Line2D.Float sind sinngemäß identisch, bis auf das sie float als Datentyp benutzen. double bietet eine höhere Präzision. Für die vollständige Methodenliste beider Klassen verweise ich auf die Java API Spezifikation.

Methode Beschreibung
Line2D.Double() konstruiert eine neue Linie mit den Koordinaten (0,0) --> (0,0)
Line2D.Double(double x1, double y1, double x2, double y2) konstruiert eine neue Linie mit den Koordinaten (x1,y1) --> (x2,y2)
Line2D.Double(Point p1, Point p2) konstruiert eine neue Linie vom Punkt p1 zum Punkt p2)
Rectangle2D getBounds2D() gibt das kleinstmögliche Rechteck zurück, daß die gesamte Linie einschließt
Point2D getP1() gibt den Startpunkt der Linie zurück
Point2D getP2() gibt den Endpunkt der Linie zurück
double getX1() gibt die X-Koordinate des Startpunktes der Linie zurück
double getY1() gibt die Y-Koordinate des Startpunktes der Linie zurück
double getX2() gibt die X-Koordinate des Endpunktes der Linie zurück
double getY2() gibt die Y-Koordinate des Endpunktes der Linie zurück
void setLine(double X1, double Y1, double X2, double Y2) setzt die Koordinaten des Start- und Endpunktes der Linie neu

Die folgende paint-Routine zeichnet eine Linie und ihr Begrenzungsrechteck in ein Graphics2D-Objekt (Zugriff auf die Quelle):


public void paint(Graphics g) {
				
				//Upcast --> mehr Funktionen in Graphics2D
				Graphics2D g2d=(Graphics2D)g;

				//Linie erzeugen
				Line2D line = new Line2D.Double( 50,50,400,200 );
				
				//Begrenzungsrechteck ermitteln
				Rectangle2D boundary = line.getBounds2D();
				
				//Linie malen
				g2d.draw( line );
				
				//Begrenzungsrechteck rot
				g2d.setPaint( Color.red );
				
				//Begrenzungsrechteck malen
				g2d.draw( boundary );
			}
Das Ergebnis sieht wie folgt aus:

[nach oben]


quadratische Kurven

Eine quadratische Kurve besteht aus zwei Endpunkten p1 und p2 und einem Kontrollpunkt k. An jedem Endpunkt liegt eine Tangente an, die durch p1 und k bzw. durch p2 und k verläuft. Diese Tangenten definieren gerade die Steigungen der quadratischen Kurve in den Punkten p1 und p2. Zwischen p1 und p2 wird auf Grundlage dieser Steigungen eine quadratische Kurve berechnet. Das folgende Bild zeigt eine quadratische Kurve:

Quadratische Kuven werden in der Java 2D API durch die abstrakte Klasse java.awt.geom.QuadCurve2D repräsentiert. Die auch hier vorhandenen inneren Klassen von QuadCurve2D heißen QuadCurve2D.Double und QuadCurve2D.Float.

Die folgende Tabelle enthält die Konstruktoren von QuadCurve2D.Double. Die Konstruktoren von QuadCurve2D.Float sind sinngemäß identisch, bis auf das sie float als Datentyp benutzen.

Methode Beschreibung
QuadCurve2D.Double() konstruiert eine quadratische Kurve mit den Endpunkten (0,0) und (0,0) sowie dem Kontrollpunkt (0,0)
QuadCurve2D.Double(double x1, double y1, double ctrlx, double ctrly, double x2, double y2) konstruiert eine quadratische Kurve mit den Endpunkten (x1,y1), (x2,y2) sowie dem Kontrollpunkt (ctrlx,ctrly).

Natürlich können auch hier wieder die Koordinaten der einzelnen Punkte durch entsprechende get-/set-Methoden ermittelt werden und auch zu den schon aus Line2D bekannten Methoden boolean contains(...) und Rectangle getBounds() gibt es hier äquivalente Methoden, die ich hier nicht noch einmal wiederhole.

Einige Methoden zum erneuten Setzen der drei für die Kurve notwendigen Punkte entnehmen Sie bitte folgender Tabelle (aus Gründen der Komplexität habe ich hier nur die innere Klasse für double betrachtet, für die float-Klasse gibt es gleichwertige Methoden):

Methode Beschreibung
void setCurve(double[] coords, int offset) setzt die X- und Y-Koordinaten von Endpunkt1, Kontrollpunkt 1 und Endpunkt 2 (in dieser Reihenfolge) anhand des Arrays coords, beginnend beim Index offset
void setCurve(double x1, double y1, double ctrlx, double ctrly, double x2, double y2) setzt die X- und Y-Koordinaten der Endpunkte und des Kontrollpunktes anhand der übergebenen Koordinaten.
void setCurve(Point2D[] pts, int offset) setzt die X- und Y-Koordinaten von Endpunkt1, Kontrollpunkt 1 und Endpunkt 2 (in dieser Reihenfolge) anhand der übergebenen Punkte.

Die folgende paint-Routine zeichnet eine quadratische Kurve in ein Graphics2D-Objekt (Zugriff auf die Quelle):


public void paint(Graphics g) {
				//Upcast --> mehr Funktionen in Graphics2D
				Graphics2D g2d=(Graphics2D)g;

				//quad.Kurve erstellen:
				QuadCurve2D myCurve=new QuadCurve2D.Double(
					20,200, //Punkt 1
					200,-90, //Kontrollpunkt k
					400,110 //Punkt 2
				);
				
				//Kurve malen
				g2d.draw( myCurve );
			}
Das Ergebnis sieht wie folgt aus:

[nach oben]


kubische Kurven

Eine kubische Kurve besteht im Gegensatz zu einer quadratischen Kurve aus zwei Endpunkten p1 und p2 und zwei Kontrollpunkten k1 und k2. An p1 und p2 liegt jeweils eine Tangente an, die durch p1 und k1 bzw. durch p2 und k2 verläuft. Diese unabhängig voneinander definierbaren Tangenten definieren die Steigungen der Kurve in den Punkten p1 und p2. Zwischen p1 und p2 wird auf Grundlage dieser Steigungen eine Parameterkurve dritten Grades berechnet. Das folgende Bild zeigt eine kubische Kurve:

Kubische Kuven werden in der Java 2D API durch die abstrakte Klasse java.awt.geom.CubicCurve2D repräsentiert. Die auch hier vorhandenen inneren Klassen von CubicCurve2D heißen CubicCurve2D.Double und CubicCurve2D.Float.

Die folgende Tabelle enthält die Konstruktoren von CubicCurve2D.Double. Die Konstruktoren von CubicCurve2D.Float sind sinngemäß identisch, bis auf das sie float als Datentyp benutzen.

Konstruktor Beschreibung
CubicCurve2D.Double() konstruiert eine kubische Kurve mit den Endpunkten (0,0) und (0,0) sowie den Kontrollpunkten (0,0) und (0,0)
CubicCurve2D.Double(double x1, double y1, double ctrlx1, double ctrly1, double ctrlx2, double ctrly2, double x2, double y2) konstruiert eine kubische Kurve mit den Endpunkten (x1,y1), (x2,y2) sowie den Kontrollpunkten (ctrlx1,ctrly1) und (ctrlx2,ctrly2).

Einige Methoden zum erneuten Setzen der vier für die Kurve notwendigen Punkte entnehmen Sie bitte folgender Tabelle (aus Gründen der Komplexität habe ich hier nur die innere Klasse für double betrachtet, für die float-Klasse gibt es gleichwertige Methoden):

Methode Beschreibung
void setCurve(double[] coords, int offset) setzt die X- und Y-Koordinaten von Endpunkt1, Kontrollpunkt 1, Endpunkt 2, Kontrollpunkt 2 (in dieser Reihenfolge) anhand des Arrays coords, beginnend beim Index offset.
void setCurve(double x1, double y1, double ctrlx1, double ctrly1, double ctrlx2, double ctrly2, double x2, double y2) setzt die X- und Y-Koordinaten der Endpunkte und der Kontrollpunkte anhand der übergebenen Koordinaten.
void setCurve(Point2D[] pts, int offset) setzt die X- und Y-Koordinaten von Endpunkt1, Kontrollpunkt 1, Endpunkt 2, Kontrollpunkt 2 (in dieser Reihenfolge) anhand der übergebenen Punkte.

Die folgende paint-Routine zeichnet eine kubische Kurve in ein Graphics2D-Objekt (Zugriff auf die Quelle):


public void paint(Graphics g) {				
				//Upcast --> mehr Funktionen in Graphics2D
				Graphics2D g2d=(Graphics2D)g;

				//quad.Kurve erstellen:
				CubicCurve2D myCurve=new CubicCurve2D.Double(
					20,150, //Punkt 1
					60,190, //Kontrollpunkt k1
					300,10, //Kontrollpunkt k2
					400,60 //Punkt 2
				);
				
				//Kurve malen
				g2d.draw( myCurve );
			}
Das Ergebnis sieht wie folgt aus:

[nach oben]


Rechtecke

Rechtecke bietet die Java 2D API in zwei Ausführungen an: mit abgerundeten Ecken und ohne abgerundete Ecken. Sie werden durch die abstrakten Klassen java.awt.geom.Rectangle2D java.awt.geom.RoundRectangle2D repräsentiert. Wieder gibt es je eine innere Klasse für float- und double-Koordinaten.

Ich betrachte hier nur die Rechtecke mit normalen Ecken. Rechtecke mit abgerundeten Ecken besitzen lediglich zwei zusätzliche Member-Variablen, die die Höhe und Breite des Bogens darstellen, der statt einer normalen Ecke gezeichnet wird. Im folgenden Beispielprogramm werden aber beide Arten von Rechtecken dargestellt.

Die folgende Tabelle enthält die Konstruktoren von Rectangle2D.Double. Die Konstruktoren von Rectangle2D.Float sind sinngemäß identisch, bis auf das sie float als Datentyp benutzen.

Konstruktor Beschreibung
Rectangle2D.Double() konstruiert ein Rechteck der mit der linken oberen Ecke bei (0,0) und einer Breite und Fläche von 0.
Rectangle2D.Double(double x, double y, double w, double h) konstruiert ein Rechteck der mit der linken oberen Ecke bei (x,y), einer Breite von w und einer Höhe von h.

Einige Methoden zum erneuten Setzen der Rechteckdaten entnehmen Sie bitte folgender Tabelle (aus Gründen der Komplexität habe ich hier nur die innere Klasse für double betrachtet, für die float-Klasse gibt es gleichwertige Methoden):

Methode Beschreibung
void setRect(double x, double y, double w, double h) reinitialisiert die Rechteckposition und seine Maße anhand der übergebenen Werte
void setRect(Rectangle2D r) übernimmt die Position und Maße des Rechtecks r für dieses Rechteck.

Die folgende paint-Routine zeichnet zwei gefüllte Rechtecke (eines davon mit abgerundeten Ecken) in ein Graphics2D-Objekt (Zugriff auf die Quelle):


public void paint(Graphics g) {
				
				//Upcast --> mehr Funktionen in Graphics2D
				Graphics2D g2d=(Graphics2D)g;
				
				// "normales Rechteck" erstellen:
				Rectangle2D r1 = new Rectangle2D.Double( 
					50,60, //Ecke links oben (X,Y)
					200,200 //Breite, Höhe
					);
				
				// Rechteck mit abgerundeten Ecken erstellen:
				RoundRectangle2D r2 = new RoundRectangle2D.Double(
					280,60, //Ecke links oben (X,Y)
					200,200, //Breite, Höhe
					50,30 //Bogenbreite, Bogenhöhe
					);
					
				// nun füllen wir die Rechtecke mit Farben:
				g2d.setPaint( Color.blue );
				g2d.fill( r1 );
				
				g2d.setPaint( Color.red );
				g2d.fill( r2 );
								
			}
Das Ergebnis sieht wie folgt aus:

[nach oben]


Ellipsen

Ellipsen werden durch die abstrakte Klasse java.awt.geom.Ellipse2D repräsentiert. Wieder gibt es je eine innere Klasse für float- und double-Koordinaten. Eine Ellipse wird durch ein sie umschließendes, minimales Rechteck definiert. Aufgrund dieser Tatsache ist sie eine Subklasse der Klasse java.awt.geom.RectangularShape, die eine Superklasse für alle geometrischen Figuren darstellt, die durch ein sie umgebendes Rechteck dargestellt werden können. Es gibt einen Durchmesser in X- und einen Durchmesser in Y-Richtung, hier "width" und "height" genannt.

Die folgende Tabelle enthält die Konstruktoren von Ellipse2D.Double. Die Konstruktoren von Ellipse2D.Float sind sinngemäß identisch, bis auf das sie float als Datentyp benutzen.

Konstruktor Beschreibung
Ellipse2D.Double() konstruiert eine neue Ellipse, deren Begrenzungsrechtecke die linke, obere Ecke bei (0,0) hat. Die Ellipse hat die Breite 0 und die Höhe 0.
Ellipse2D.Double(double x, double y, double w, double h) konstruiert eine neue Ellipse anhand des Rechtecks x,y,w,h

Einige Methoden zum erneuten Setzen der Ellipsendaten entnehmen Sie bitte folgender Tabelle. Die Methode setFrame(...)zum Setzen der Ellipsengröße und -position stammt in diesem Fall von der Superklasse RectangularShape. Aus Gründen der Komplexität habe ich hier nur die innere Klasse für double betrachtet, für die float-Klasse gibt es gleichwertige Methoden.

Methode Beschreibung
void setFrame(Rectangle2D r) redefiniert Position und Abmessungen der Ellipse anhand der Rechtecks r
setFrame(double x, double y, double w, double h) redefiniert Position und Abmessungen der Ellipse anhand des Rechtecks x,y,w,h

Die folgende paint-Routine erzielt eine Art "Tunnel-Effekt", indem sie ineinander verschachtelte Ellipsen, deren Farbe sich mit abnehmender Größe von schwarz nach weiß ändert, in ein Graphics2D-Objekt zeichnet (Zugriff auf die Quelle):


public void paint(Graphics g) {				
				//Upcast --> mehr Funktionen in Graphics2D
				Graphics2D g2d=(Graphics2D)g;
				
				double w;double h;double x;double y; //Ellipsenrechteck
				w=450;h=200;x=10;y=50; //Ellipsenmaße
				
				double f=0.99;//Verkleinerungsfaktor pro Iteration
				
				//Ellipse mit initialen Werten erzeugen
				Ellipse2D e = new Ellipse2D.Double( x,y,w,h );
				
				//Rot-,Grün- und Blauanteil d. akt. Farbe
				int rd;int gr;int bl; 
				
				double limit=100; //Mindestbreite d. Ellipse
				double runs=Math.log(limit/w)/Math.log(f); //Anzahl Durchläufe
				double count=0; //Zähler
								
				while( w > limit ) //solange w>Mindestbreite
				{
					//Farbe proportional zum Zähler count heller machen
					rd=(int)((count/runs)*255);
					gr=(int)((count/runs)*255);
					bl=(int)((count/runs)*255);
					
					//Farbe setzen				
					g2d.setPaint( new Color( rd,gr,bl ) );
					g2d.fill(e); //Ellipse malen

					//verkleinern d. Ellipse um den Faktor f
					y += (h-f*h)/2; //Y-Position anpassen
					w *= f; //Höhe, Breite verkleinern
					h *= f;
					
					//setzen der neuen Ellipsenmaße
					e.setFrame( new Rectangle2D.Double(x,y,w,h) );
					
					//Durchlaufzähler++
					count+=1d;
				}
Das Ergebnis sieht wie folgt aus:

[nach oben]


weitere Klassen

Außer den vorgestellten Geometrie-Klassen gibt es noch weitere Klassen, z.B. java.awt.Arc2D, die geometrische Formen repräsentieren. Um den Rahmen dieser Ausarbeitung nicht zu sprengen, habe ich auf die Vorstellung dieser Klassen verzichtet. Auf konstruktive Flächengeometrie (java.awt.geom.Area) gehe ich im nächsten Kapitel ein.


XML und Java ... Die Java 2D API ... Zum Seitenanfang