11.5Das Innere und Äußere einer Form
Vor dem Zeichnen sammelt das Grafiksystem die Objekte in einem Kontext. Er bestimmt für die Form den Zeichenbereich (engl. clipping), die Transformationen, die Komposition von Objekten. Die diversen drawXXX(…)- und fillXXX(…)-Methoden von Graphics2D berücksichtigen beim Zeichnen Farb- und Texturangaben, Dicken der Umrisslinien, Linienmuster und Weiteres. Unterscheiden müssen wir zwischen zwei Eigenschaften:
11.5.1Farben und die Paint-Schnittstelle
Die Farben für das Innere geben Objekte vom Typ java.awt.Paint an. Paint ist eine Schnittstelle, die unter anderem folgende Klassen aus dem java.awt-Paket implementieren:
Color: Repräsentiert sRGB-Farben und Alpha-Werte (Transparenz).
GradientPaint, LinearGradientPaint, RadialGradientPaint: Füllt Formen (Shape-Objekte) mit Farbverläufen.
SystemColor: Repräsentiert Farben, wie sie vom Benutzer in den Systemeinstellungen definiert sind.
Der Zuweisung eines Paint-Objekts auf den aktuellen Graphics2D-Kontext dient die Methode setPaint(Paint).
11.5.2Farben mit der Klasse Color
Ein java.awt.Color-Objekt repräsentiert üblicherweise einen Wert aus dem sRGB-Farbraum (Standard-RGB), kann aber auch andere Farbräume über den Basistyp java.awt.color.ColorSpace darstellen (wir werden das nicht weiter verfolgen).
Die Klasse Color stellt Konstanten wie BLACK, WHITE, für Standardfarben und einige Konstruktoren sowie Anfragemethoden wie getRed(), … bereit. Außerdem gibt es Methoden, die abgewandelte Color-Objekte liefern – das ist nötig, da Color-Objekte wie String oder File immutable sind.
implements Paint, Serializable
Color(float r, float g, float b)
Erzeugt ein Color-Objekt mit den Grundfarben Rot, Grün und Blau. Die Werte müssen im Bereich 0.0 bis 1.0 liegen, sonst folgt eine IllegalArgumentException.Color(int r, int g, int b)
Erzeugt ein Color-Objekt mit den Grundfarben Rot, Grün und Blau. Die Werte müssen im Bereich 0 bis 255 liegen, sonst folgt eine IllegalArgumentException.Color(int rgb)
Erzeugt ein Color-Objekt aus dem rgb-Wert, der die Farben Rot, Grün und Blau kodiert. Der Rotanteil befindet sich unter den Bits 16 bis 23, der Grünanteil in 8 bis 15 und der Blauanteil in 0 bis 7. Da ein Integer immer 32 Bit breit ist, ist jede Farbe durch 1 Byte (8 Bit) repräsentiert. Die Farbinformationen werden nur aus den 24 Bit genommen. Sonstige Werte werden einfach nicht betrachtet und mit einem Alpha-Wert gleich 255 überschrieben.Color(int r, int g, int b, int a)
Color(float r, float g, float b, float a)
Erzeugt ein Color-Objekt mit Alpha-Wert für Transparenz.static Color decode(String nm) throws NumberFormatException
Liefert die Farbe von nm. Die Zeichenkette ist hexadezimal als 24-Bit-Integer kodiert, etwa #00AAFF.
11.5.3Composite und XOR *
Ein Composite ist eine Zusammenfügung der zu zeichnenden Elemente und des Hintergrunds. Auf dem Graphics2D-Objekt setzt setComposite(Composite) den Modus, wobei bisher AlphaComposite die einzige direkte Implementierung der Schnittstelle Composite ist. Ein AlphaComposite-Objekt bestimmt, wie die Überblendung aussehen soll.
[zB]Beispiel
Zeichne ein Bild image mit dem Alpha-Wert alpha:
g2.setComposite( AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha) );
g2.drawImage( image, 0, 0, this );
Der XOR-Modus
Die zweite Einstellung, wie Farben auf das Ziel wirken, bestimmt der XOR-Modus, der ein spezieller Composite ist, mit dem Pixel, die zweimal gezeichnet werden, ihre Ursprungsfarbe wieder annehmen.
abstract void setComposite(Composite comp)
Setzt das Composite-Objekt, das die Verschmelzung der folgenden Zeichenoperationen mit dem Hintergrund definiert.abstract void setXORMode(Color c)
Setzt die Pixel-Operation auf XOR.
11.5.4Dicke und Art der Linien von Formen bestimmen über Stroke *
Eine noch fehlende Eigenschaft ist die der Umrisslinie, Stroke genannt. Zu den Eigenschaften einer Umrisslinie zählen:
die Dicke (engl. width)
die Art, wie Liniensegmente beginnen und enden (engl. end caps)
die Art, wie aufeinandertreffende Linien verbunden werden (engl. line joins)
Die Stroke-Schnittstelle
Die Umrisseigenschaften bestimmen Objekte vom Typ java.awt.Stroke; die Methode setStroke(Stroke) auf dem Graphics2D-Kontext setzt sie. Alle nachfolgenden Graphics2D-Methoden wie draw(Shape), drawLine(…) usw. berücksichtigen diese Umrisslinie anschließend.
Die Schnittstelle Stroke schreibt nur eine Operation vor:
Shape createStrokedShape(Shape p)
Liefert die Umrandung für ein Shape p.
Bisher gibt es in Java nur eine Standardimplementierung der Schnittstelle: BasicStroke.
[zB]Beispiel
Zeichne die folgenden Formen mit einer Dicke von 10 Pixeln:
Abbildung 11.11Vererbungsbeziehung von BasicStroke und Stroke
Linienenden (end caps)
Besonders bei breiten Linien ist es interessant, wie eine allein stehende Linie endet. Sie kann einfach aufhören oder auch abgerundet sein. Drei Konstanten bestimmen diesen Linienende-Typ:
BasicStroke.CAP_ROUND: Rundet das Ende mit einem Halbkreis ab.
BasicStroke.CAP_SQUARE: Setzt einen rechteckigen Bereich an.
Die Typen CAP_ROUND und CAP_SQUARE erweitern die Linie um ein Stück, das halb so groß wie die Dicke der Linie ist:
Listing 11.6com/tutego/insel/ui/g2d/EndCaps.java, paintComponent()
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke( new BasicStroke( 20, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER ) );
g2.drawLine( 30, 50, 200, 50 );
g2.setStroke( new BasicStroke( 20, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER ) );
g2.drawLine( 30, 150, 200, 150 );
g2.setStroke( new BasicStroke( 20, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER ) );
g2.drawLine( 30, 100, 200, 100 );
}
Zwar gibt es von BasicStroke fünf Konstruktoren, aber nur einen einfachen, der Linienenden (immer unterschiedlich in dem Beispiel) und Linienverbindungen (hier BasicStroke.JOIN_MITER) gleichzeitig bestimmt haben möchte.
Abbildung 11.12Unterschiedliche Linienenden
Linienverbindungen (line joins)
Wenn Linien nicht allein stehen, sondern etwa wie in einem Dreieck oder Rechteck verbunden sind, stellt sich die Frage, wie diese Verbindungspunkte gezeichnet werden. Das bestimmen ebenfalls drei Konstanten:
BasicStroke.JOIN_BEVEL: Zieht eine Linie zwischen den beiden äußeren Endpunkten.
BasicStroke.JOIN_MITER: Erweitert die äußeren Linien so weit, bis sie sich treffen.
Listing 11.7com/tutego/insel/ui/g2d/LineJoins.java, paintComponent()
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
BasicStroke stroke = new BasicStroke( 20, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL );
g2.setStroke( stroke );
Path2D shape = new GeneralPath();
shape.moveTo( 25, 25 );
shape.lineTo( 50, 100 );
shape.lineTo( 75, 25 );
g2.draw( shape );
//
stroke = new BasicStroke( 20, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER );
g2.setStroke( stroke );
shape = new GeneralPath();
shape.moveTo( 25+100, 25 );
shape.lineTo( 50+100, 100 );
shape.lineTo( 75+100, 25 );
g2.draw( shape );
//
stroke = new BasicStroke( 20, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND );
g2.setStroke( stroke );
shape = new GeneralPath();
shape.moveTo( 25+200, 25 );
shape.lineTo( 50+200, 100 );
shape.lineTo( 75+200, 25 );
g2.draw( shape );
}
Abbildung 11.13Unterschiedliche Linienverbindungen BEVEL, MITER, ROUND
Falls der Typ der Linienverbindungen JOIN_MITER ist, kann mit einem spitzen Winkel die Verbreiterung sehr lang werden. Die Variable miterlimit beim Konstruktor kann die maximale Länge beschränken, sodass die beiden Linien oberhalb einer gewissen Größe mit JOIN_BEVEL enden.
Füllmuster (dash)
Auch die Muster, mit denen die Linien oder Kurven gezeichnet werden, lassen sich ändern. Dazu erzeugen wir vorher ein float-Feld und übergeben es einem Konstruktor.
Abbildung 11.14Kodierung der Füllmuster
Die folgenden Zeilen erzeugen ein Rechteck mit einem einfachen Linienmuster. Es sollen zehn Punkte gesetzt und zwei Punkte frei sein. Damit auch die Muster abgerundet werden, muss CAP_ROUND gesetzt sein:
Listing 11.8com/tutego/insel/ui/g2d/DashWithBasicStroke.java, Ausschnitt 1
BasicStroke stroke = new BasicStroke( 2,
BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,
1,
dash, 0 );
g2.setStroke( stroke );
g2.draw( new Rectangle2D.Float( 50, 50, 50, 50 ) );
Als letztes Argument hängt am Konstruktor von BasicStroke noch eine Verschiebung. Dieser Parameter bestimmt, wie viele Pixel im Muster übersprungen werden sollen. Geben wir dort für unser Beispiel etwa 10 an, so beginnt die Linie gleich mit zwei nicht gesetzten Pixeln. Eine 12 ergibt eine Verschiebung wieder an den Anfang. Bei nur einer Zahl im Feld sind der Abstand der Linien und die Breite einer Linie genauso lang, wie diese Zahl angibt. Bei gepunkteten Linien ist das Feld also 1. Hier eignet sich ein anonymes Feld ganz gut, wie die nächsten Zeilen zeigen:
Listing 11.9com/tutego/insel/ui/g2d/DashWithBasicStroke.java, Ausschnitt 2
BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL,
1, new float[]{ 1 }, 0 );
Bei feinen Linien sollten wir das Weichzeichnen besser ausschalten.
Abbildung 11.15Zwei Linienmuster