Rendering



XML und Java ... Die Java 2D API ... Next: Bilder

[nach oben]


Einführung

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.

[nach oben]


Compositing

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:

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.

Damit das 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:

Das Testprogramm liefert folgende Ergebnisgrafik:

[nach oben]


AlphaComposite

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:

Dabei bedeuten die verwendeten Variablenbezeichner folgendes: Jede der in 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

Diese Instanz kann dann an die 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.

[nach oben]


Transforming

Das Graphics2D-Objekt ermöglicht vier Arten von geometrischen Transformationen:

  1. Translation (translation)
  2. Rotation (rotation)
  3. Scherung (shearing)
  4. Skalierung (scaling)
Diese Transformationen haben gemeinsam, daß sowohl vor als auch nach Ausführung der Transformation alle Punkte, die vorher auf einer Geraden lagen, auch noch danach auf einer Geraden liegen. Diese Art von Transformation nennt man Affine Transformation (affin=ähnlich). Fast jede affine Transformation ist umkehrbar durch Ausführen der zugehörigen inversen Transformation.

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

Für jede Art von Transformation gibt es aber auch eine statische Klassenmethode, die ein Transformationsobjekt der jew. Transformationsart zurückliefern kann.

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).
Des weiteren existieren die Methoden 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:

  1. Scherung mit sx=sy=0.5
  2. Rotation um 22,5° nach rechts (=PI/8)
  3. Translation mit (tx,ty)=(20,30);
  4. Skalierung: Vergrößern mit den Faktoren 1.2 (X) und 1.5 (Y)
Das transformierte Rechteck wird in grün dargestellt (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 );
		
				//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:

[nach oben]


Clipping

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.

Das 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:

Bahamas nach Clipping


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