Die Graphics2D
-Klasse weiß grundsätzlich nichts über Geometrie. Dennoch muß es einen
Weg geben, um elementare geometrische Objekte wie Rechtecke oder Ellipsen darzustellen, wie
das letzte Kapitel gezeigt hat.
Die Entwickler der Java 2D API haben sich dabei - vom logischen Ansatz her ähnlich wie das
Paint
-Interface -
eine Lösung ausgedacht, die eine Vereinheitlichung verschiedenster geometrischer Formen
ermöglicht: Das Interface jawa.awt.Shape
.
Das Shape-Interface schreibt Methoden vor, durch die man die meisten geometrische Objekte
- hier auch Umrisse genannt -
beschreiben kann. Durch diese Methoden wird Graphics2D
die Darstellung dieser Objekte ermöglicht.
Graphics2D
kann also im Prinzip lediglich ein geometrisches Objekt zeichnen oder füllen, welches
das Shape
-Interface implementiert.
Alle Klassen, die geometrische Objekte repräsentieren - hierzu zählen die Klassen aus dem
Geometrie-Paket jawa.awt.geom
wie z.B. Rectangle2D
, Ellipse2D
oder
Line2D
, implementieren das Shape
-Interface.
Komplexe geometrische Formen haben gemeinsam, daß sie sich aus mehreren einfacheren Formen
zusammensetzen lassen. Diese Tatsache macht sich das Shape
-Interface zu Nutze. Ein
Shape
-Objekt beinhaltet unter anderem ein PathIterator
-Objekt, welches zur geometrischen
Beschreibung eines Umrisses dient. jawa.awt.geom.PathIterator
ist selbst auch
ein Interface.
Jedes Shape
-Objekt besitzt ein Begrenzungsrechteck, welches das kleinstmögliche
Rechteck darstellt, daß den gesamten Umriß umschließt (zugreifbar mit Rectangle2D getBounds2D()
).
Das Shape
-Interface schreibt folgende Methoden vor:
Methode | Beschreibung |
---|---|
boolean contains(double x, double y) |
gibt zurück, ob der Punkt mit den Koordinaten x
und y innerhalb des Begrenzungsrechtecks
des Umrisses liegt.
|
boolean contains(double x, double y, double w, double h) |
gibt zurück, ob das Rechteck mit x,y,w,h innerhalb
des Begrenzungsrechtecks des Umrisses liegt.
|
boolean contains(Point2D p) |
gibt zurück, ob der Punkt p im Umriß enthalten ist.
|
boolean contains(Rectangle2D r) |
gibt zurück, ob das Rechteck r vollständig im Umriß
enthalten ist.
|
Rectangle getBounds() |
gibt das Begrenzungsrechteck für den Umriß als
Rectangle zurück.
|
Rectangle2D getBounds2D() |
gibt das Begrenzungsrechteck für den Umriß als
Rectangle2D zurück.
|
PathIterator getPathIterator(AffineTransform at) |
gibt ein PathIterator -Objekt zurück, daß den Umriß nach Ausführung der
affinen Transformation at
beschreibt.
|
PathIterator getPathIterator(AffineTransform at, double flatness) |
gibt ein PathIterator -Objekt zurück, daß den Umriß nach Ausführung der
affinen Transformation at
beschreibt. Die Kurvensegmente werden durch gerade Segmente
angenähert. flatness gibt an, um wieviele Punkte ein solches
Näherungssegment maximal von dem originalen Kurvensegment abweichen darf.
Diese Methode ist wichtig für Graphics2D -Implementationen, die keine
Kurvensegmente darstellen können.
|
boolean intersects(double x, double y, double w, double h) |
gibt zurück, ob das Rechteck x,y,w,h den Umriß schneidet.
|
boolean intersects(Rectangle2D r) |
gibt zurück, ob der Umriß das Rechteck r schneidet.
|
Das PathIterator
-Objekt stellt eine iterative Beschreibung eines Umrisses durch
Geraden und Kurven (2. und 3. Ordnung) dar. Es gibt jeweils ein aktuelles Segment,
dessen Lage und Typ durch die currentSegment(...)
-Methoden ermittelt
werden können (s. Tabellen zu PathIterator
). Die Gesamtheit aller Segmente bezeichnet man als Pfad.
Ein einzelnes Segment besitzt einen der folgenden in PathIterator
definierten,
als int
codierten, Typen:
Segmenttyp | Bedeutung |
---|---|
SEG_CLOSE |
schließt den Pfad durch Ziehen einer Linie zu dem Punkt, der durch das letzte SEG_MOVETO angesprungen wurde. |
SEG_CUBICTO |
beschreibt ein Segment, das durch eine kubische Kurve, die durch 3 Punkte defniert wird, vom vorherigen Punkt aus gezeichnet wird. |
SEG_LINETO |
beschreibt ein Segment, das durch eine Gerade, die durch einen Punkt defniert wird, vom vorherigen Punkt aus gezeichnet wird. |
SEG_MOVETO |
unterbricht den Pfad und setzt ihn an einem beliebigen Punkt wieder fort. |
SEG_QUADTO |
beschreibt ein Segment, daß durch eine quadratische Kurve, die durch 2 Punkte defniert wird, vom vorherigen Punkt aus gezeichnet wird. |
Des weiteren bietet PathIterator
Konstanten an, die die Winding Rule des gesamten
Pfades beschreiben. Die Winding Rule gibt bei Pfaden mit sich schneidenden Segmenten an,
welche Teile des durch den Pfad definierten Umrisses innerhalb des Umrisses liegen und deshalb
bei einem Aufruf von Graphics2D.fill(Shape s)
gefüllt würden. Die Winding Rules
sind gerade bei komplexen Pfaden nicht leicht nachzuvollziehen und somit ist es schwierig,
manuell herauszufinden, welche Flächen des Pfades nun gefüllt werden und welche nicht.
Folgende Winding Rules werden von PathIterator
in Form von Konstanten angeboten. Die
mathematisch-abstrakt gehaltenen Erläuterungen entnehmen Sie bitte der Spezifikation von
PathIterator
in der Java 2D API-Spezifikation:
Das PathIterator
-Interface schreibt folgende Methoden vor, die im Wesentlichen zur Abfrage
der einzelnen Segmente und zum Voranschreiten innerhalb des Pfades dienen:
Methode | Beschreibung |
---|---|
int currentSegment(double[] coords) |
liefert den Typ des aktuellen Segments (s. Tabelle oben)
sowie dessen Koordinaten in einem double -Array.
coords enthält...
|
int currentSegment(float[] coords) |
wie vorherige Methode, aber liefert ein float -Array |
int getWindingRule() |
gibt die Winding-Rule (s. Tabelle oben) für den Pfad zurück |
Wie wir sehen, ist die Implementierung aller Shape
- und PathIterator
-Methoden
eine mühsame Angelegenheit. Deswegen gibt es zur
Vereinfachung eine
Klasse jawa.awt.geom.GeneralPath
, die das Shape
-Interface bereits implementiert und
die somit auch das von Shape
geforderte PathIterator
-Objekt zurückgeben kann. Bei der Erstellung
eigener geometrischer Objekte ist aus Gründen der Einfachheit also der Einsatz der GeneralPath
-Klasse
sehr empfehlenswert.
Die Klasse java.awt.GeneralPath
repräsentiert einen geometrischen Pfad, der aus
einzelnen Segementen besteht. Die Segmente können Linien, quadratische Kurven oder kubische
Kurven sein und werden nacheinander dem GeneralPath
-Objekt hinzugefügt,
bis der gewünschte Umriß erstellt ist. Da GeneralPath
das Shape
-Interface implementiert,
kann der erstellte Umriß dann sofort und ohne vorherige Umwandlungen in das Graphics2D
-Objekt
gezeichnet werden.
Die Konstruktoren für GeneralPath
entnehmen Sie bitte folgender Tabelle:
Konstruktor | Beschreibung |
---|---|
GeneralPath() |
konstruiert einen leeren Pfad mit der non-zero Winding Rule. |
GeneralPath(int rule) |
konstruiert einen neuen Pfad mit einer Winding Rule (s. letztes Kapitel),
die angibt, auf welche Weise berechnet werden soll, ob ein Punkt in der
Fläche des Pfades liegt. Hier muß GeneralPath.WIND_EVEN_ODD oder
GeneralPath.WIND_NON_ZERO angegeben werden. |
GeneralPath(Shape s) |
erzeugt einen neuen Pfad, der den Umriß s beschreibt. |
Wenn man ein GeneralPath
-Objekt erstellt hat, möchte man dem noch leeren Pfad Segemente hinzufügen.
Der Startpunkt des Pfades liegt immer bei (0,0).
Im schrittweisen Aufbauprozeß eines Pfades ist es wichtig, zu wissen, daß man auch diskontinuierliche
Pfade erstellen kann, indem man dem Pfad einen neuen Punkt (mit moveTo()
hinzufügt,
der aber nicht mit dem letzten Punkt durch eine der drei Kurvenarten verbunden wird.
Das Hinzufügen von Segementen geschieht mit den folgenden Methoden:
Methode | Beschreibung |
---|---|
void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) |
fügt dem Pfad ein kubisches Kurvensegment hinzu. Die Punkte
(x1,y1) und (x2,y2) dienen dabei als Kontrollpunkte,
der letzte hinzugefügte Punkt und (x3,y3) dienen als Endpunkte.
|
void lineTo(float x, float y) |
fügt dem Pfad ein Liniensegment hinzu, daß vom letzten hinzugefügten
Punkt zum Punkt (x,y) reicht.
|
void quadTo(float x1, float y1, float x2, float y2) |
fügt dem Pfad ein quadratisches Kurvensegment hinzu, daß vom letzten
hinzugefügten Punkt zum Punkt (x2,y2) reicht. Der
Kontrollpunkt ist (x1,y1) .
|
void append(Shape s, boolean connect) |
fügt den Umriß s dem Pfad hinzu. Ist connect true ,
so wird vom letzten hinzugefügten Punkt ein Liniensegment zum Anfangspunkt
des Pfades von s gezogen.
|
void append(PathIterator pi, boolean connect) |
wie vorherige Methode, aber der hinzuzufügende Pfad wird aus einem
Objekt bezogen, welches das PathIterator -Interface implementiert.
|
Die folgende paint
-Methode erstellt einen neuen Pfad und demonstriert den Einsatz
der verschiedenen Segementtypen. Der Pfad wird in ein Graphics2D
-Objekt gezeichnet
(Zugriff auf die Quelle):
public void paint(Graphics g) {
//Upcast --> mehr Funktionen in Graphics2D
Graphics2D g2d=(Graphics2D)g;
//Pfad erzeugen:
GeneralPath gp = new GeneralPath();
//Punkte hinzufügen
gp.moveTo( 16, 12 );
gp.lineTo( 41, 81 );
gp.quadTo( 43, 86 ,44, 81 );
gp.lineTo( 53, 60 );
gp.quadTo( 54, 58, 53, 57 );
gp.lineTo( 47, 44 );
gp.lineTo( 48, 41 );
gp.quadTo( 53, 36, 51, 36 );
gp.lineTo( 45, 35 );
gp.lineTo( 37, 17 );
gp.quadTo( 36, 12, 43, 12 );
gp.quadTo( 49, 12, 50, 15 );
gp.lineTo( 75, 81 );
gp.quadTo( 76, 83, 78, 81 );
gp.lineTo( 87, 58 );
gp.lineTo( 82, 44 );
gp.quadTo( 77, 41, 84, 39 );
gp.lineTo( 91, 39 );
gp.quadTo( 93,39, 93, 41 );
gp.quadTo( 94, 45, 95, 41 );
gp.lineTo( 106, 10 );
gp.lineTo( 83, 10 );
gp.lineTo( 91, 32 );
gp.quadTo( 91, 34, 89, 35 );
gp.lineTo( 80, 35 );
gp.lineTo( 69, 12 );
gp.quadTo( 68, 9, 66, 10 );
gp.lineTo( 18, 10 );
gp.quadTo( 13, 9, 16, 12 );
//Pfad unterbrechen und kubische Kurve hinzufügen
gp.moveTo( 12, 100 );
gp.curveTo( 0,90, 115, 105, 110, 95 );
//Pfad skalieren (="größer machen")
g2d.scale( 5, 5 );
// Verlaufsfarbe erzeugen
g2d.setPaint( new GradientPaint( 10,10, new Color(0,0,255),
80,80, Color.white ));
// Pfad füllen
g2d.fill( gp );
// schware Farbe setzen
g2d.setPaint( Color.black );
// Umriß des Pfades malen
g2d.draw( gp );
}
Das Ergebnis sieht wie folgt aus:
Unter konstruktiver Flächengeometrie versteht man in der Java 2D API die Verknüpfung der Flächen von
Shape
-Objekten. Die Verknüpfung erfolgt analog zu Mengenverknüfpungen aus der Mathematik. Die Elemente
der zu verknüpfenden Mengen sind hier die Punkte zweier Flächen.
Die Klasse java.awt.geom.Area
ermöglicht solche Verknüpfungen. Mit Hilfe des Konstruktors
Area(Shape s)
Area
-Objekt, daß die Fläche eines Shape
-Objektes darstellt. Die folgenden
Methoden aus Area
stehen zur Verknüpfung von Flächen zur Verfügung:
add(Area a): bildet die Vereinigung mit der Fläche a
.
subtract(Area a): bildet die Differenz mit der Fläche a
.
exclusiveOr(Area a): verknüpft die Fläche durch exklusives Oder mit der Fläche a
.
Die folgende paint
-Methode
(Zugriff auf die Quelle) führt zwei Flächenadditionen,
Flächensubtraktionen, und eine xor-Verknüpfung aus:
public void paint(Graphics g) {
//Upcast --> mehr Funktionen in Graphics2D
Graphics2D g2d=(Graphics2D)g;
//Geometrische Formen erstellen
Ellipse2D e = new Ellipse2D.Float( 20,20, 400,200 );
Rectangle2D r = new Rectangle2D.Float( 300,80, 190,180 );
Rectangle2D r2 = new Rectangle2D.Float( 80, 50, 50, 50 );
Rectangle2D r3 = new Rectangle2D.Float( 90,150, 100, 100 );
//Area-Objekte erstellen
Area a_e = new Area( e );
Area a_r = new Area( r );
Area a_r2 = new Area ( r2 );
Area a_r3 = new Area( r3 );
//Flächen verknüpfen, Ergebnis in a:
Area a = new Area();
a.add( a_e ); //addieren der Ellipse
a.add( a_r ); //addieren Rechteck r
a.subtract( a_r2 ); //subtrahieren Rechteck r2
a.exclusiveOr( a_r3 ); //exklusives Oder mit Rechteck r3
//eine Verlaufsfarbe setzen
g2d.setPaint( new GradientPaint( 0,0, Color.blue,
500, 500, Color.green )
);
//Ergebnis füllen (dies ist möglich, weil auch Area Shape implementiert)
g2d.fill( a );
}
Das Ergebnis sieht wie folgt aus: