Eine der Stärken der Java 2D API ist, daß Umrisse, Text und Bilder in vielerlei Hinsicht gleich
behandelt werden können. So können die vier Stufen transforming, compositing, clipping
und rendering hints der Rendering Pipeline, die das
Graphics2D
-Objekt zur Verfügung stellt, für jede der drei Objektklassen angepaßt werden.
Die Vorgänge zur Durchführung dieser Anpassung beschreibe ich in diesem Kapitel.
Als Compositing bezeichnet man den Vorgang, der unter Berücksichtigung bestimmter Regeln
(z.B. Transparenzeffekte) ein Bild mit einem anderen Bild kombiniert. In einem Graphics2D
-Objekt
wird beim Hinzufügen eines neuen Objektes (z.B. ein Shape
) das gerenderte Objekt mit der bereits bestehenden
Zeichenfläche kombiniert. Das Ergebnis ist eine veränderte Zeichenfläche, die das hinzugefügte Objekt enthält.
Das Ergebnis beim Hinzufügen von Objekten zur Zeichenfläche ist abhängig von der im Graphics2D
-Objekt
momentan gesetzten Compositing Rule. Diese wird durch ein Objekt repräsentiert, welches
das Interface java.awt.Composite
implementiert. Das Composite
-Interface schreibt nur eine Methode
vor:
CompositeContext
createContext(ColorModel srcColorModel, ColorModel dstColorModel, RenderingHints hints)
Diese Methode gibt ein java.awt.CompositeContext
-Objekt zurück. CompositeContext
ist wieder ein
Interface und schreibt folgende Methoden vor:
Methode | Beschreibung |
---|---|
void compose(Raster src, Raster dstIn, WritableRaster dstOut) | kombiniert die Raster src und dstIn und schreibt das Ergebnis in das beschreibbare
Raster dstOut .
|
public void dispose() | gibt die allozierten Resourcen für diesen Kontext frei. |
CompositeContext
-Objekt auch weiß, wie es die Raster src
und
dstOut
kombinieren soll, ist es sinnvoll, in Composite.createContext()
an den Konstruktor des zurückzugebenden CompositeContext
-Objektes die hier bekannten
Farbmodelle zu übergeben.
Das Setzen einer Kompositionsregel im Graphics2D
-Objekt erfolgt durch einen Aufruf
der Methode Graphics2D.setComposite( Composite c )
.
Die Verwendung des Composite
-Interface habe ich anhand eines Programmbeispiels
dokumentiert. Das Programm "TestMyComposite.java" demonstriert den Einsatz einer von
mir implementierten Kompositionsregel, die die Farbwerte von Zeichenfläche und dem
zu zeichnenden Objekt mit dem UND-Operator verknüpft. Durch Anwendung des UND-Operators
erzielt man eine Verdunklung der Schnittflächen zweier Bilder (restriktive Wirkung).
Zur Ausführung werden folgende Quellcodedateien benötigt:
Composite
-InterfaceCompositeContext
-Interface
Auch für das Composite
-Interface gibt es im JDK 1.3 eine konkrete Implementation.
Es handelt sich um die Klasse java.awt.AlphaComposite
. Diese Klasse
vereint Regeln zur Verknüpfung von Quell- und Zielpixeln, z.B. unter Verwendung von
Transparenzeffekten.
Die in dieser Klasse implementierten Regeln sind eine Untermenge der Porter-Duff-Rules, (T. Porter, T. Duff: "Compositing Digital Images"). Die hier verwendeten Regeln zur Verrechnung von Quell- und Zielpixeln lauten:
AlphaComposite
festgelegten Regeln beruht auf einem konkreten Tupel (Fs,Fd).
Es stehen folgende Regeln, die durch Konstanten in AlphaComposite
repräsentiert werden,
zur Verfügung(Beispielprogramm s.unten)
Regel | Bedeutung |
---|---|
CLEAR |
(Fs,Fd)=(0,0) --> Zielpixel werden gelöscht (da Cd=0, Ad=0) |
SRC_OVER |
(Fs,Fd)=(1,1-As) --> Plaziert die Quelle über dem Ziel |
DST_OVER |
(Fs,Fd)=(1-As,1) --> Plaziert das Ziel über der Quelle |
SRC |
(Fs,Fd)=(1,0) --> Plaziert die Quelle opak über dem Ziel |
SRC_IN |
(Fs,Fd)=(Ad,0) --> Ersetzt Ziel durch Quelle |
DST_IN |
(Fs,Fd)=(0,As) --> Ersetzt Quelle durch Ziel |
SRC_OUT |
(Fs,Fd)=(1-Ad,0) --> Zeigt den Teil der Quelle, der außerhalb des Ziels liegt |
DST_OUT |
(Fs,Fd)=(0,1-As) --> Zeigt den Teil des Ziels, der außerhalb der Quelle liegt |
Eine Instanz von AlphaComposite
erhält man durch eine der beiden Methoden
static AlphaComposite getInstance(int rule)
static AlphaComposite getInstance(int rule, float alpha)
Graphics2D.setComposite()
-Methode übergeben
werden (s. oben), um sie als Kompositionsregel zu benutzen.
Die folgende paint
-Methode (Zugriff auf die Quelle)
zeichnet mehrmals je zwei sich überschneidende Rechtecke mit jeder der genannten Regeln.
Bitte beachten Sie, daß einige Regeln hier gleiche Ergebnisse haben, da die
weiße Hintergrundfarbe ebenfalls als Bestandteil der Zielpixel (Destination) gilt und nicht als transparent
gewertet wird.
public void paint(Graphics g) {
//Upcast --> mehr Funktionen in Graphics2D
Graphics2D g2d=(Graphics2D)g;
//Testrechteck
Rectangle2D r = new Rectangle2D.Float();
float alpha = 0.8f; //Alpha-Wert
float w=50;float h=50; //Rechteck-Maße
int[] rules = new int[8]; //alle möglichen Compositing Rules:
rules[0] = AlphaComposite.SRC_OVER;
rules[1] = AlphaComposite.DST_OVER;
rules[2] = AlphaComposite.CLEAR;
rules[3] = AlphaComposite.SRC;
rules[4] = AlphaComposite.SRC_IN;
rules[5] = AlphaComposite.DST_IN;
rules[6] = AlphaComposite.SRC_OUT;
rules[7] = AlphaComposite.DST_OUT;
//Alle Regeln durchlaufen, je zwei sich überlappende
//Rechtecke nebeneinander füllen
for( int i=0; i<8; i++ ) {
//fülle erstes Rechteck mit SRC
r.setRect( (float)(i+1)*70,30,w,h );
g2d.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC,alpha));
g2d.setPaint(Color.blue);
g2d.fill(r);
//das zweite Rechteck darüber legen mit jew. Regel
g2d.setComposite(AlphaComposite.getInstance( rules[i] , alpha ));
r.setRect( (float)(i+1)*70+10,40,w,h );
g2d.setPaint(Color.red);
g2d.fill(r);
}
Das Ergebnis sieht so aus.
Das Graphics2D
-Objekt ermöglicht vier Arten von geometrischen Transformationen:
Im mathematischen Sinne spricht von von einer Affinen Abbildung, die man als Multiplikation eines Spaltenvektors (x,y,1) mit einer Matrix, welche die zur Transformation notwendigen Parameter enthält, darstellen kann. Es ergeben sich die transformierten Koordinaten x' und y':
[ x'] [ m00 m01 m02 ] [ x ] [ m00x + m01y + m02 ] [ y'] = [ m10 m11 m12 ] [ y ] = [ m10x + m11y + m12 ] [ 1 ] [ 0 0 1 ] [ 1 ] [ 1 ]
Affine Transformationen werden in der Java 2D API durch die Klasse
java.awt.geom.AffineTransform
repräsentiert. Alle vier Arten von Transformationen
sind in dieser Klasse vereint.
Ein neues AffineTransform
-Objekt liefert z.B. der Konstruktor
AffineTransform()
.
Folgende Methoden aus AffineTransform
ermöglichen das Arbeiten mit Transformationen:
Methode | Beschreibung |
---|---|
void concatenate(AffineTransform Tx) |
Konkatentiert diese Transformation mit der Transformation
Tx |
AffineTransform createInverse() |
gibt die zu dieser Transformation inverse Transformation zurück |
static AffineTransform getRotateInstance(double theta) |
gibt eine Transformation zurück, die einer Rotation mit dem
Winkel theta (im Bogenmaß!) entspricht. Die Rotation
erfolgt um den aktuellen Koordinatenursprung. |
static AffineTransform getRotateInstance(double theta, double x, double y) |
wie vorherige Methode, aber die Rotation erfolgt um den Punkt
(x,y ).
|
static AffineTransform getScaleInstance(double sx, double sy) |
gibt eine Transformation zurück, die einer Skalierung mit dem Faktor
sx in X-Richtung und sy in Y-Richtung
entspricht. |
static AffineTransform getShearInstance(double shx, double shy) |
gibt eine Transformation zurück, die einer Scherung entspricht. Ein Punkt (x,y) wird dabei in Abhängigkeit von seiner Y-Koordinate um den Faktor shx in X-Richtung verschoben. Der Punkt P(2,3) wird mit
shx=4 und shy=2 abgebildet auf den Punkt
P'(2+3*4,3+2*2)=(14,7). Man berücksichtige hierbei auch die umgekehrte
Ausrichtung der Y-Achse.
|
static AffineTransform getTranslateInstance(double tx, double ty) |
gibt eine Transformation zurück, die einer Translation entspricht.
Der Urpsrung des Graphics2D -Koordinatensystems wird dabei um
tx Einheiten in X-Richtung und ty Einheiten in
Y-Richtung verschoben. |
void setToIdentity() |
setzt diese Transformation auf eine Identitätstransformation zurück (also eine Transformation, die jeden Punkt (x,y) auf sich selbst abbildet). |
void setToRotation(double theta) |
macht diese Transformation zu einer Rotation (s.oben). |
void setToRotation(double theta, double x, double y) |
macht diese Transformation zu einer Rotation um
den Punkt (x,y ). |
void setToScale(double sx, double sy) |
macht diese Transformation zu einer Skalierung (s.oben). |
void setToShear(double shx, double shy) |
macht diese Transformation zu einer Scherung (s.oben). |
void setToTranslation(double tx, double ty) |
macht diese Transformation zu einer Translation (s.oben). |
void shear(double shx, double shy),
void translate(double tx, double ty), void scale(double sx, double sy)
und void rotate(double theta)
, die die das jeweilige Transformationsobjekt
mit einer weiteren Scherung, Translation, Skalierung oder Rotation verketten.
Man bedenke, daß z.B. die Folge der Transformationen "Rotation, Translation" und die Folge "Translation, Rotation" unterschiedliche Ergebnisse haben können!
Die folgende paint
-Methode (Zugriff auf die Quelle)
zeichnet zunächst ein Rechteck (rot) und führt dann dann folgende affine Transformationen
im Graphics2D
-Koordinatensystem durch:
public void paint(Graphics g) {
//Upcast --> mehr Funktionen in Graphics2D
Graphics2D g2d=(Graphics2D)g;
g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON );
//neue Transformation erstellen
AffineTransform tx = new AffineTransform();
//ein Testrechteck:
RoundRectangle2D r = new RoundRectangle2D.Float(50,50,200,200,10,10);
//r vor Transformation:
g2d.setColor( Color.red );
g2d.fill( r );
//Transformationen konkatenieren
tx.shear( 0.5,0.5 );
tx.rotate( Math.PI/8, 50,50 );
tx.translate( 20, 30 );
tx.scale( 1.2,1.5 );
//Transformation anwenden
g2d.setTransform( tx );
//r nach Transformation:
g2d.setColor( new Color(0,180,0,200) );
g2d.fill( r );
}
Das Ergebnis sieht so aus:
Beim Clipping wird der Bereich der Zeichenfläche, in dem gemalt wird, eingeschränkt.
Als praktisch erweist es sich hierbei, daß man diesen Bereich, den Clipping Shape,
durch ein Shape
-Objekt definieren kann.
Graphics2D.clip( Shape s )
setzt man den Clipping Shape. Wenn bereits
ein solcher existiert hat, dann ist der neue Clipping Shape die Schnittfläche von s
und dem schon vorhandenen Clipping ShapeShape Graphics2D.getClip()
gibt den aktuellen Clipping Shape zurück.Graphics2D.setClip( Shape s )
setzt den Shape
s als aktuellen
Clipping ShapeDas folgende Beispiel für eine paint
-Routine erstellt aus einem
Textumriß den Clipping Shape für das Graphics2D
-Objekt, in welches dann ein
Bild gezeichnet wird (Zugriff auf die Quelle):
public void paint(Graphics g) {
//Upcast --> mehr Funktionen in Graphics2D
Graphics2D g2d=(Graphics2D)g;
g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
//ein schönes Bild laden
BufferedImage img=loadTheImage();
//Schrift erzeugen
Font f = new Font("Arial",Font.ITALIC|Font.BOLD,260);
//Umriß eines Strings als GlyphVector speichern
GlyphVector gv = f.createGlyphVector(
g2d.getFontRenderContext()
, "Enjoy");
//Hintergrund schwarz
g2d.fill( new Rectangle2D.Float(0,0,800,600) );
//Clipping Shape von GlyphVector holen
Shape clipping_shape = gv.getOutline();
//Clipping Shape soll bei (0,0) beginnen
g2d.translate( -clipping_shape.getBounds().getX(),
-clipping_shape.getBounds().getY()
);
//jetzt Clipping Shape horizontal und vertikal zentrieren
g2d.translate( 400-clipping_shape.getBounds().getWidth()/2,
300-clipping_shape.getBounds().getHeight()/2+50 );
g2d.scale( 1, 1.4 );//Clipping Shape skalieren...
g2d.setClip( clipping_shape );//und setzen
//das Bild zentriert malen (OHNE Transformation)
g2d.setTransform( new AffineTransform() );
g2d.drawImage(img, null, 400-img.getWidth()/2,
300-img.getHeight()/2);
}
Das Ergebnis sieht so aus: