Farben



XML und Java ... Die Java 2D API ... Next: Geometrie I: vordefinierte Klassen

[nach oben]


Einleitung

Die Java 2D API bietet dem Entwickler mehrere Möglichkeiten zur Definition von Farben an.
Es gibt bereits drei mitgelieferte Klassen, die verschiedene Arten von Farben repräsentieren:

  1. java.awt.Color: einfache Farben
  2. java.awt.GradientPaint: lineare Farbverläufe
  3. java.awt.TexturePaint: Texturen
Für die meisten Anwendungsfälle ist die Funktionalität dieser Klassen vollkommen ausreichend. Allen drei Klassen ist gemeinsam, daß sie das Paint-Interface implementieren (java.awt.Paint). Dieses Interface gibt eine Reihe von Methoden vor, die eine konkrete Klasse für eine Farbe implementieren muß.
Es ist also auch möglich, daß der Entwickler sich eigene Farben definiert. Dies wird er genau dann tun, wenn die bereits im JDK enthaltenen Farbklassen nicht seinen Anforderungen entsprechen. Es gibt z.B. keine Klasse, die einen radialen Farbverlauf bereitstellt. Weiter unten werde ich zeigen, wie man durch Implementieren des Paint-Interfaces eigene Farben definieren kann.

[nach oben]


monochrome Farben erzeugen

Die Klasse java.awt.Color liefert monochrome Farben nach dem RGB-Farbschema. Zusätzlich zu den Red-, Green- und Blue-Komponenten kann man auf Wunsch auch einen Alpha-Wert bei der Konstruktion eines Color-Objekts übergeben, der die Transparenz der Farbe angibt. Standardmäßig sind alle Farben opak (undurchsichtig).

Die folgende Tabelle zeigt die Konstruktoren der Color-Klasse:

KonstruktorBeschreibung
Color( int r, int g, int b ) r,g,b stehen für die Ausprägungen der Red-, Green- und Blue-Komponente und sind int-Werte mit dem Wertebereich {0,1,...,255}. 255 ist die stärkste Ausprägung einer Farbkomponente.
Color( float r, float g, float b ) r,g,b sind float-Werte mit dem Wertebereich {0.0,...,1.0}. 1.0 ist die stärkste Ausprägung einer Farbkomponente.
Color( int rgb ) Die RGB-Werte befinden sich in drei Bytes des Integers rgb:

Bit 0-7: Blue-Komponente
Bit 8-15: Green-Komponente
Bit 16-23: Red-Komponente
Wertebereiche: {0,1,...,255}
Color( int r, int g, int b, int a ) r,g,b stehen für die Ausprägungen der Red-, Green- und Blue-Komponente, a für den Alpha-Wert, und sind allesamt int-Werte mit dem Wertebereich {0,1,...,255}.
Ist der Alpha-Wert 0, so ist die Farbe vollkommen transparent, ist er 255, so ist die Farbe opak.
Color( float r, float g, float b, float a ) r,g,b stehen für die Ausprägungen der Red-, Green- und Blue-Komponente, a für den Alpha-Wert, und sind allesamt float-Werte mit dem Wertebereich {0,...,1.0}.
Ist der Alpha-Wert 0.0, so ist die Farbe vollkommen transparent, ist er 1.0, so ist die Farbe opak.
Color( int rgba, boolean hasAlpha ) Die RGB-Werte stehen in den Bytes 0 bis 2 von rgba, der Alpha-Wert im Byte 3.

Man kann auch auf die in Color vordefinierten Farbkonstanten vom Typ Color zurückgreifen, um die Definition einer eigenen Farbe zu umgehen. So ist z.B. Color.white äquivalent zu dem Konstruktoraufruf new Color(255,255,255). Die vordefinierten Farben aus der Color-Klasse entnehmen Sie bitte der Java API-Spezifikation.

[nach oben]


monochrome Farben zerlegen

Nachdem wir nun wissen, wie man einfache Farben erzeugt, betrachten wir nun das Gegenstück zu dieser Operation - die Zerlegung von Farben in ihre einzelnen Bestandteile.

Die folgenden Methoden liefern die Rot-, Grün-, Blau- oder Alpha-Komponente eines Color-Objektes. Die Methodenbezeichner sind selbsterklärend.

Die Rückgabewerte sind wieder Element der Menge {0,1,...,255}.

[nach oben]


Farbverläufe

Die Paint-Implementation GradientPaint bietet dem Entwickler folgende Arten von Farbverläufen:

  1. lineare, azyklische Farbverläufe
  2. lineare, zyklische Farbverläufe
Linear bedeutet hier, daß ein gleichmäßiger Übergang von einer Farbe C1 zu einer Farbe C2 stattfindet. Die "Richtung" des Verlaufs kann ebenfalls definiert werden, da ein Farbverlauf stets anhand zweier Punkt P1 und P2 stattfindet.

Ein Verlauf zwischen den Punkten P1(10,10) und P2(20,20) mit den Farben Color.white und Color.blue verläuft z.B. "von links oben nach rechts unten". Eine gedachte Gerade zwischen P1 und P2 bildet mit der X- und der Y-Achse unseres Koordinatensystems einen 45°-Winkel.

Ein azyklischer Verlauf ist ein einmaliger Verlauf von der Farbe C1 zur Farbe C2 zwischen den Punkten P1 und P2. Vor P1 haben alle Pixel die Farbe C1, zwischen P1 und P2 findet ein gleichmäßiger Übergang statt und nach P2 haben alle Pixel die Farbe C2.
Bei einem zyklischen Verlauf dagegen beginnt nach P2 ein erneuter Verlauf, und zwar von C2 zu C1. Im nächsten Abschnitt folgt ein Verlauf von C1 zu C2, dann von C2 zu C1 usw. Es ergibt sich also theoretisch eine unendliche Wiederholung des Farbverlaufs, welche in der Realität allerdings durch die Zeichenfläche begrenzt wird.

Für lineare Farbverläufe existieren u.a. folgende Konstruktoren:

Konstruktor Beschreibung
GradientPaint(float x1, float y1, Color color1, float x2, float y2, Color color2) x1 und y1 stehen für die Koordinaten des Punktes P1, x2 und y2 für die Koordinaten des Punktes P2, color1 bzw. color2 für Ausgangsfarbe C1 und Zielfarbe C2.
Dieser Konstruktor erzeugt einen azyklischen Verlauf.
GradientPaint(float x1, float y1, Color color1, float x2, float y2, Color color2, boolean cyclic) Wie vorheriger Konstruktor, allerdings kann hier über das Argument cyclic eingestellt werden, ob der Verlauf zyklisch sein soll (wenn cyclic true ist.)
Es existieren auch äquivalente Konstruktoren mit Point2D-Objekten als Parameter, die ich hier nicht eingefügt habe, da ja lediglich das Konzept der Farbverläufe verdeutlicht werden soll.

Die folgende paint-Methode füllt ein Rechteck mit einem azyklischen, linearen Farbverlauf:
(Zugriff auf die Quelle)

			public void paint(Graphics g) {
				
				//Upcast --> mehr Funktionen in Graphics2D
				Graphics2D g2d=(Graphics2D)g;
			
				//Rechteck erzeugen:	
				Rectangle2D rect = new Rectangle2D.Double(100,100,200,200);
				
				//Verlaufsfarbe erzeugen:
				GradientPaint gp = new GradientPaint( 	110,110, Color.blue, //P1,C1
									290,290, Color.white,//P2,C2
									false //azyklisch
								);
				
				g2d.setPaint( gp ); //Verlaufsfarbe setzen
				g2d.fill( rect ); //Rechteck mit Verlauf füllen
			}

Das Ergebnis (links mit Beschriftung, rechts ohne Beschriftung)

Die folgende paint-Methode füllt ein Rechteck mit einem zyklischen, linearen Farbverlauf
(Zugriff auf die Quelle):


			public void paint(Graphics g) {
				
				//Upcast --> mehr Funktionen in Graphics2D
				Graphics2D g2d=(Graphics2D)g;
				
				//Rechteck erzeugen:	
				Rectangle2D rect = new Rectangle2D.Double(100,100,200,200);
				
				//Verlaufsfarbe erzeugen:
				GradientPaint gp = new GradientPaint( 	110,110, Color.blue, //P1,C1
									150,150, Color.white,//P2,C2
									true //zyklisch
								);
				
				g2d.setPaint( gp ); //Verlaufsfarbe setzen
				g2d.fill( rect ); //Rechteck mit Verlauf füllen

			}

Das Ergebnis (links mit Beschriftung, rechts ohne Beschriftung):

[nach oben]


Texturen

Die Klasse TexturePaint aus dem Paket java.awt ermöglicht das Malen mit Texturen. Der Konstruktor

erstellt ein neues TexturePaint-Objekt. Das Bild txtr wird wie eine Kachel von der Größe des Rechtecks anchor (in User-Koordinaten) in ein Raster gezeichnet. Dieses kann dann zum Zeichnen und Füllen benutzt werden.

Das Bild txtr sollte möglichst klein sein, da bei jedem Zeichnen des Bildes in das PaintContext-Objekt von TexturePaint die Daten des Bildes kopiert werden.

Das folgende Codebeispiel zeigt den Gebrauch der TexturePaint-Klasse:


			public void paint(Graphics g) {
				//Upcast --> mehr Funktionen in Graphics2D
				Graphics2D g2d=(Graphics2D)g;
				
				BufferedImage img=loadTheImage(); //ein Bild laden
				
				//Texturfarbe erstellen, Anchor-Rechteck hat genau die Bildmaße
				TexturePaint tp = new TexturePaint( img, new Rectangle2D.
								         Double(0,0,
								                img.getWidth(),
								                img.getHeight())
								   );
				
				//Texturfarbe setzen
				g2d.setPaint(tp);
				
				//Rechteck füllen
				g2d.fill( new Rectangle2D.Double( 50,50,300,300 ));
			}

Das Ergebnis sieht wie folgt aus (mit Beschriftung):

[nach oben]


Das Paint-Interface

Das Paint-Interface dient dem Entwickler dazu, sich eigene Farben zu definieren. In diesem Abschnitt möchte ich zunächst allgemein und dann anhand eines Beispiels zeigen, wie man das Paint-Interface einsetzt.

Gegeben ist die folgende Vererbungshierarchie von Interfaces, in die das Paint-Interface integriert ist:

java.awt.Transparency
  |
  +--java.awt.Paint

Man sieht, daß das Paint-Interface ein Superinterface Transparency hat. Es muß also vom Entwickler eine Klasse deklariert werden, die alle geforderten Methoden aus Paint und Transparency definiert.
Paint schreibt eine Methode (s. Tabelle) public PaintContext createContext(...) vor, die ein Objekt einer Klasse liefert, die das PaintContext-Interface implementiert. Solch eine Klasse ist also ebenfalls zu definieren.

Um eine eigene Farbe zu definieren, müssen zwei eigene Klassen definiert werden:

  1. Eine Klasse, z.B. MyPaint, die das Paint-Interface (und somit auch das Superinterface Transparency implementiert) .
  2. Eine Klasse, die das PaintContext-Interface implementiert, als Bezeichner wäre z.B. MyPaintContext denkbar.

Ein PaintContext-Objekt enthält letztendlich ein Raster, also eine Art Bild, daß das Füllmuster für ein bestimmten Bereich eines Graphics2D-Objektes beinhaltet.
Dieser Technik bedienen sich auch die im JDK enthaltenen Paint-Implementationen GradientPaint, TexturePaint und Color.

Wenn im Graphics2D-Objekt etwas gezeichnet wird, dann wird von einem PaintContext-Objekt lediglich für einen bestimmten, durch ein Rechteck definierten Bereich, der das zu zeichnende Objekt eingrenzt, ein Raster berechnet, daß den bei der Zeichenoperation zu setzenden Pixeln eine bestimmte Farbe zuordnet. Dieses Raster ist eine Instanz von Raster, die von der durch PaintContext vorgeschriebenen Methode Raster getRaster(...) (s. Tabelle unten) geliefert wird.

Als aktuelle Farbe kann man im Graphics2D-Objekt ein beliebiges Objekt einer Klasse setzen, die das Paint-Interface implementiert. Das Setzen einer Farbe erfolgt durch einen Aufruf von Graphics2D.setPaint( Paint p ).

Wird z.B. ein Rechteck mit einem linearen Farbverlauf gefüllt, so stammt dieser aus einem vorher berechneten Raster, welches bei Aufruf der setPaint()-Methode des Graphics2D-Objektes von der getRaster()-Methode des jeweiligen PaintContext-Objektes erzeugt wurde.
Das Raster für monochrome Farben besteht z.B. immer aus nur einer Farbe (z.B. blau, wenn die Zeichenfarbe mit setPaint(Color.blue) gesetzt wurde).

Die folgende Tabelle zeigt, welche Methoden für das Paint-Interface definiert werden müssen, um eine eigene Paint-Implementation zu erhalten.

gefordert von Interface... Methode Beschreibung
Transparency int getTransparency() Gibt die Transparenzeigenschaft der eigenen Farbe zurück (als int codiert). Als Rückgabewert sollte eine der Klassenkonstanten aus Transparency verwendet werden:
  • BITMASK: für Farben, die entweder vollständig transparent oder opak sind (entweder Alpha = 0.0 oder Alpha = 1.0)
  • OPAQUE: für opake Farben (Alpha = 1.0)
  • TRANSLUCENT: für Farben, die einen Alpha-Wert zwischen 0.0 und 1.0 haben
Paint public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) gibt eine PaintContext-Implementation zurück, die ein Raster für das Rechteck userBounds (in User-Koordinaten) berechnen kann.
deviceBounds gibt dieses Rechteck in Gerätekoordinaten an.
xform gibt die Transformation an, mit der user-Koordinaten auf Gerätekoordinaten abgebildet werden.
hints gibt die in der Zeichenfläche gesetzten Rendering-Hints an.

Die nächste Tabelle zeigt, welche Methoden das PaintContext-Interface fordert.

gefordert von Interface... Methode Beschreibung
PaintContext void dispose() führt evtl. Aufräumarbeiten durch, z.B. könnte Speicher für ein Raster freigegeben werden, falls dieses als Member-Variable existiert.
PaintContext ColorModel getColorModel() gibt das von diesem PaintContext verwendete Farbmodell zurück (ColorModel.getRGBdefault() , das Standard RGB-Farbmodell, sollte für die meisten Fälle ausreichen).
PaintContext public Raster getRaster(int x,int y,int w,int h) Diese Methode gibt das schon beschriebene Raster für das Rechteck, daß durch x,y (linke obere Ecke), w und h (Breite und Höhe) definiert wird, zurück. Die Parameter sind bereits Gerätekoordinaten umgerechnet.

Den Einsatz des Paint-Interfaces habe ich anhand eines Beispieles dokumentiert. Zur Ausführung werden folgende drei Quelldateien benötigt:

  1. MyPaint.java: Paint-Implementation
  2. MyPaintContext.java: PaintContext-Implementation
  3. TestMyPaint.java: Hauptprogramm zum Testen

Implementiert wurde ein radialer Farbverlauf. Diese Art von Verlauf hat einen Mittelpunkt (im Testprogramm (200,200)), der an den MyPaint-Konstruktor übergeben werden kann. Die Kommentare in den genannten Quellcode-Dateien erläutern die Funktionsweise.

Die Ausführung von TestMyPaint.java hat folgende Ergebnisgrafik (Ausschnitt):

Das Raster des PaintContext-Objektes wurde für den Bereich des zu füllenden Rechteckes berechnet, der Mittelpunkt des Farbverlaufs wurde schon bei der Konstruktion des MyPaint-Objektes festgelegt.

Durch das Implementieren des Paint-Interfaces ist der Entwickler also nicht auf vordefnierte Farbklassen beschränkt, sondern kann durch Definition einiger weniger Methoden schnell eigene Farben kreieren.


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