Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Java ist auch eine Insel von Christian Ullenboom
Buch: Java ist auch eine Insel (Galileo Computing)
gp Kapitel 14 Grafikprogrammierung mit dem AWT
gp 14.1 Das Abstract-Window-Toolkit
gp 14.1.1 Java Foundation Classes
gp 14.2 Fenster unter grafischen Oberflächen
gp 14.2.1 Fenster öffnen
gp 14.2.2 Größe und Position des Fensters verändern
gp 14.2.3 Fenster- und Dialog-Dekoration
gp 14.3 Das Toolkit
gp 14.3.1 Einen Hinweis beepen
gp 14.4 Grundlegendes zum Zeichnen
gp 14.4.1 Die paint()-Methode
gp 14.4.2 Auffordern zum Neuzeichnen mit repaint()
gp 14.4.3 Fensterinhalte ändern und die ereignisorientierte Programmierung
gp 14.5 Punkte, Linien und Rechtecke aller Art
gp 14.5.1 Linien
gp 14.5.2 Rechtecke
gp 14.6 Alles was rund ist
gp 14.7 Polygone und Polylines
gp 14.7.1 Die Polygon-Klasse
gp 14.7.2 N-Ecke zeichnen
gp 14.7.3 Vollschlanke Linien zeichnen
gp 14.8 Zeichenketten schreiben
gp 14.8.1 Einen neuen Zeichensatz bestimmen
gp 14.8.2 Ableiten eines neuen Fonts aus einem gegebenen Font
gp 14.8.3 Zeichensätze des Systems ermitteln
gp 14.8.4 Die Klasse FontMetrics
gp 14.8.5 True Type Fonts
gp 14.9 Clipping-Operationen
gp 14.10 Farben
gp 14.10.1 Zufällige Farbblöcke zeichnen
gp 14.10.2 Farbanteile zurückgeben
gp 14.10.3 Vordefinierte Farben
gp 14.10.4 Farben aus Hexadezimalzahlen erzeugen
gp 14.10.5 Einen helleren oder dunkleren Farbton wählen
gp 14.10.6 Farbmodelle HSB und RGB
gp 14.10.7 Die Farben des Systems
gp 14.11 Bilder anzeigen und Grafiken verwalten
gp 14.11.1 Eine Grafik zeichnen
gp 14.11.2 Grafiken zentrieren
gp 14.11.3 Laden von Bildern mit dem MediaTracker beobachten
gp 14.11.4 Kein Flackern durch Double-Buffering
gp 14.11.5 Bilder skalieren
gp 14.12 Programm-Icon setzen
gp 14.12.1 VolatileImage
gp 14.13 Grafiken speichern
gp 14.13.1 Bilder im GIF-Format speichern
gp 14.13.2 Gif speichern mit dem ACME-Paket
gp 14.13.3 JPEG-Dateien mit dem Sun-Paket schreiben
gp 14.13.4 Java Image Management Interface (JIMI)
gp 14.14 Von Produzenten, Konsumenten und Beobachtern
gp 14.14.1 Producer und Consumer für Bilder
gp 14.14.2 Beispiel für die Übermittlung von Daten
gp 14.14.3 Bilder selbst erstellen
gp 14.14.4 Die Bildinformationen wieder auslesen
gp 14.15 Filter
gp 14.15.1 Grundlegende Eigenschaft von Filtern
gp 14.15.2 Konkrete Filterklassen
gp 14.15.3 Mit CropImageFilter Teile ausschneiden
gp 14.15.4 Transparenz
gp 14.16 Alles wird bunt mit Farbmodellen
gp 14.16.1 Die abstrakte Klasse ColorModel
gp 14.16.2 Farbwerte im Pixel mit der Klasse DirectColorModel
gp 14.16.3 Die Klasse IndexColorModel
gp 14.17 Drucken
gp 14.17.1 Drucken mit dem einfachen Ansatz
gp 14.17.2 Ein PrintJob
gp 14.17.3 Drucken der Inhalte
gp 14.17.4 Komponenten drucken
gp 14.17.5 Den Drucker am Parallelport ansprechen
gp 14.18 Java 2D-API
gp 14.18.1 Grafische Objekte zeichnen
gp 14.18.2 Geometrische Objekte durch Shape gekennzeichnet
gp 14.18.3 Eigenschaften geometrischer Objekte
gp 14.18.4 Transformationen mit einem AffineTransform-Objekt
gp 14.19 Graphic Layers Framework
gp 14.20 Grafikverarbeitung ohne grafische Oberfläche
gp 14.20.1 Xvfb-Server
gp 14.20.2 Pure Java AWT Toolkit (PJA)


Galileo Computing

14.16 Alles wird bunt mit Farbmodellendowntop

Als wir uns mit dem Produzenten- und Konsumentenmodell bei Image-Objekten beschäftigt haben, standen die Daten der Pixel immer in einem Byte- oder Integer-Feld. Eher übersprungen wurde das Farbmodell bei MemoryImageSource und einem create Image(). Die Einträge der Felder sind Pixel, und die Werte standen für Farbinformationen, genauer gesagt für Rot, Grün und Blau. Wir haben uns bisher wenig Gedanken über das Format gemacht und stillschweigend angenommen, dass diese in 24 Bit abgelegt sein müssen. Dies muss jedoch nicht so sein, und die Interpretation der Farbwerte in einem Informationswort bestimmt ein Farbmodell. Für Farbmodelle gibt es in Java die Klasse ColorModel. Mit der Klasse lassen sich dann aus einem Pixel die roten, grünen, blauen und transparenten Anteile bestimmen. Der transparente Teil, auch Alpha-Komponente genannt, bestimmt, in welcher Intensität die Farbinformationen wirken. Alpha-Werte lassen sich nur in Zusammenhang mit Bildern anwenden. Mit der Graphics-Klasse lässt sich ein Alpha-Wert nicht einstellen, der dann alte Zeichenoperationen beeinflusst. Bei den Farbmodellen ist der Anteil der Transparenz genauso lang wie ein Farbwert, nämlich acht Bit. Ein Wert von 255 sagt aus, dass der Farbwert zu 100% sichtbar ist. Ist der Wert 0, so ist die Farbe nicht zu sehen.

Java macht das Programmierleben so plattformunabhängig wie möglich. Bei wenigen oder vielen Farben auf der Zielplattform wird eine optimale Annäherung an unsere Wunschfarben errechnet. So können wir alles in 24 Bit Farbtiefe errechnen, die Dislay-Komponente sucht die wichtigsten Farben heraus und fasst Gruppen ähnlicher Farben zusammen.


Galileo Computing

14.16.1 Die abstrakte Klasse ColorModeldowntop

Die abstrakte Klasse ColorModel beschreibt alle Methoden für konkrete Farbklassen, so dass die Informationen über die Farbwerte und die Transparenz erreichbar sind. Obwohl die Klasse abstrakt ist, besitzt sie zwei Konstruktoren, die von den Unterklassen benutzt werden. Direkte Unterklassen sind ComponentColorModel, IndexColorModel und PackedColorModel.


abstract class java.awt.image.ColorModel
implements Transparency

gp ColorModel( int pixel_bits, int bits[],
ColorSpace cspace, boolean hasAlpha, boolean isAlphaPremultiplied,
gp int transparency, int transferType )
ColorModel(int bits)

Der zweite Konstruktor ist praktisch, da dieser nur die Farbtiefe in Bits erwartet. Diese abstrakte Klasse besitzt jedoch die statische Fabrik-Methode getRGBdefault(), die ein ColorModel-Objekt zurückliefert. Das Standardfarbmodell, auch sRGB genannt, ist ein Farbmodell, welches die Werte als 24-Bit-Tupel mit den Komponenten Alpha, Rot, Grün und Blau enthält. Dieses Farbmodell lässt sich etwa für ein Memory-Image einsetzen. Der erste Konstruktor ist noch leistungsfähiger und seit Java 1.2 dabei. Mit seiner Hilfe muss ein Farbwert nicht zwingend in einem Integer kodiert sein.

Die Methode getPixelSize() liefert die Farbtiefe eines Farbmodells. Das Standardmodell besitzt eine Tiefe von 32 Bit (24 für die Farben und dann noch den Alpha-Kanal). So gibt auch die folgende Zeile als Anwort auf die Frage nach der Anzahl der Farben im Standardmodell 32 Bit aus:

System.out.println( ColorModel.getRGBdefault().getPixelSize() );

Die Hauptaufgabe einer Farbmodell-Klasse ist die Auswertung der Farbinformationen aus einem Speicherwort. Mit drei Methoden lassen sich die verschiedenen Farben auslesen. getRed(int pixel), getGreen(int pixel) und getBlue(int pixel), hinzu kommt noch getAlpha(int pixel). Jede dieser Methoden ist abstrakt und liefert eine Ganzzahl mit dem Farbwert zurück. Wie wir später sehen werden, ist das einfachste Modell genau jenes, das wir bisher immer benutzt haben. Dieses liest nämlich genau von den Stellen 24, 16 und 8 die Farbwerte aus. Da die Methoden abstrakt sind, müssen Unterklassen dieses Verhalten programmieren.

Eine weitere Methode ist getRGB(), welche ein int mit allen Farben im entsprechenden Farbformat zurückliefert. Die Implementierung basiert auf den Anfrage-Methoden.

public int getRGB(int pixel) {
  return (getAlpha(pixel) << 24)
      | (getRed(pixel) << 16)
      | (getGreen(pixel) << 8)
      | (getBlue(pixel) << 0);
}

Im Folgenden eine Auflistung der wichtigsten Methoden:


abstract class java.awt.image.ColorModel
implements Transparency

gp abstract int getAlpha( int pixel )
Liefert den Alpha-Wert im Bereich 0 bis 255.
gp abstract int getBlue( int pixel )
Liefert den Blauanteil des Pixels.
gp ColorSpace getColorSpace()
Liefert den Farbraum, der mit dem ColorModel verbunden ist.
gp int[] getComponents( int pixel, int components[], int offset )
Liefert ein Feld mit nicht normalisierter Farb- und Alpha-Komponente für ein Pixel.
gp abstract int getGreen( int pixel )
Liefert den Grünanteil.
gp int getNumColorComponents()
Gibt die Anzahl der Farben zurück.
gp int getNumComponents()
Liefert die Anzahl der Komponenten (mit Alpha).
gp int getPixelSize()
Wie viele Pixel beschreiben eine Farbe?
gp abstract int getRed( int pixel )
Liefert den Rotanteil.
gp int getRGB( int pixel )
Gibt Farb- und Alpha-Komponente des Pixels im sRGB-Farbmodell wieder.
gp static ColorModel getRGBdefault()
Liefert ein DirectColorModel mit dem sRGB-Modell.
gp int getTransparency()
Liefert die Art der Transparenz. Dies ist entweder OPAQUE, BITMASK oder TRANSLUCENT. Es sind Konstanten aus der Schnittstelle Transparency. Sie können aber auch über ColorModel verwendet werden, da ColorModel diese Schnittstelle implementiert.
gp boolean hasAlpha()
Fragt an, ob das Farbmodell Transparenz unterstützt.
gp boolean isCompatibleRaster( Raster raster )
Liefert true, falls das Raster mit dem Farbmodell kompatibel ist.

Nun lassen sich auf der Basis dieser Klassen verschiedene Farbmodelle entwerfen. Einige sind von den Entwicklern der Java-Bibliotheken schon vorgefertigt, wie etwa eine Farbklasse, die die Informationen gleich im Pixel selbst speichert, wie im Beispiel RGB, oder eine Klasse, die einen Index auf einen Farbwert verwaltet. Als eigene Ergänzung können wir Farbklassen implementieren, die Graustufen direkt unterstützen oder etwa andere Farbräume wie HSB1 (Hue, Saturation, Brightness). Die einzige Aufgabe, die uns als Implementierer der abstrakten Methoden übrig bleibt, ist, die Farbwerte aus dem Pixelwert zu extrahieren. Im Fall von HSB ist das einfach. Die Methoden getRed(), getGreen() und getBlue() müssen nur aus dem internen HSB-Wert den Anteil liefern.


Galileo Computing

14.16.2 Farbwerte im Pixel mit der Klasse DirectColorModeldowntop

Mit Hilfe der Klasse DirectColorModel werden die Farbwerte Rot, Grün, Blau und Alpha direkt aus dem Farbtupel extrahiert. Die Klasse gehört zu einer der größten im Image-Paket. Als Beispiel für das direkte Format kennen wir Standard-RGB. Für dieses gilt, dass die Farben jeweils acht Bit in Anspruch nehmen. Das muss aber nicht so sein, und im Konstruktor von DirectColorModel lässt sich bestimmen, wie und an welcher Stelle die Bits für die Farben sitzen. Wir dürfen dies jedoch nicht damit verwechseln, dass wir die Anzahl der Bits angeben. Nur die Positionen sind möglich. Daraus ergibt sich auch, dass die Werte zusammenhängend sind und nicht etwa Folgendes auftreten kann: 0xrrbgbg. Die Bitanzahl kann aber für die Farben unterschiedlich sein. Auch der Alpha-Wert kann frei gewählt werden. Für das Standardmodell ergibt sich eine einfache Zeile:

DirectColorModel rgbModel = new DirectColorModel(32,
   0xff0000, 0x00ff00, 0x0000ff, 0xff000000);

Ist das Objekt einmal angelegt, so sind nun die Anfrage-Methoden wie getRed() möglich, da DirectColorModel als konkrete Klasse, von der auch ein Exemplar erzeugt werden kann, diese abstrakten Methoden alle überschreibt und mit Implementierung versieht. Eine wichtige Eigenschaft dieser Methoden ist, dass sie final sind und ihren Farbwert mit dem Alpha-Wert kombinieren. Da sie final sind, können sie von Unterklassen nicht mehr überschrieben werden. Letzteres verlangt aber die aktuelle Implementierung der AWT-Bibliothek.


Beispiel Implementierung von getRed()
final public int getRed(int pixel) {
  int r = ((pixel & maskArray[0]) >>> maskOffsets[0]);
  if (scaleFactors[0] != 1.)
    r = (int)(r * scaleFactors[0]);
  if (isAlphaPremultiplied) {
    int a = getAlpha(pixel);
    r = (a == 0) ? 0 : (r * 255/a);
  }
  return r;
}

Im Parameter pixel ist die Farbe Rot an einer Bitposition (meistens ab 24 Bit) abgelegt. Damit wir diesen Wert auslesen und mit dem Alpha-Wert kombinieren können, muss er zunächst ausmaskiert werden. Daher wird pixel mit der Maske verknüpft, so dass nur die Bits übrig bleiben, die auch wirklich die Farbe Rot beschreiben. Anschließend verschieben wir die Rot-Pixel so weit nach rechts, dass die Grün- und Blau-Werte verschwinden. Die Felder maskArray und maskOffsets sowie scaleFactors sind in der direkten abstrakten Oberklasse PackedColorModel angelegt. Doch bleiben wir bei getRed(). Hier sehen wir noch deutlich, wie der Alpha-Wert in die Berechnung mit eingeht. Ist der Farbwert 0, so ist auch das Ergebnis 0. Ist er ungleich 0, so wird die Farbe nach dem Apha-Wert gewichtet. Der Skalierungsfaktor skaliert die Werte auf 256. Denn haben wir beispielsweise nur zwei Bits für einen Farbwert, dann müssen wir mit 128 multiplizieren, um wieder eine Acht-Bit-Darstellung zu bekommen.

Abbildung
Hier klicken, um das Bild zu Vergrößern


Galileo Computing

14.16.3 Die Klasse IndexColorModeltoptop

Im Gegensatz zur Klasse DirectColorModel verwaltet ein IndexColorModel die Farben und Transparenzen nicht im Pixel, sondern in einer eigenen Tabelle, die auch Color-Map oder Palette genannt wird. Das Modell ist vergleichbar mit dem Dateiformat GIF. Dort stehen maximal 256 Farben in einer Tabelle zur Verfügung und alle Punkte in einem GIF-Bild müssen einer dieser Farben entsprechen. Eine GIF-Datei mit zwei Farben definiert etwa eine Farbe mit schweinchenrosa und eine zweite Farbe mit hornhautumbra. Der Pixel selbst ist dann nur ein Index auf einen Eintrag. Dieses Verfahren ist sehr speicherschonend, ein Kriterium, das vor ein paar Jahrzehnten noch zählte. An Stelle von 24 Bit für einen Pixel wird der Index etwa zehn Bit breit gemacht und stellt dann bis zu 1.024 Farben dar. Das ist immerhin eine Reduktion des Bildschirmspeichers um die Hälfte. Leider sind damit aber auch hohe Berechnungskosten verbunden. Für eine Verwendung dieser Klasse spricht die Abstraktion von den konkreten Farben. Ein Beispiel dafür wäre ein Fraktalprogramm. Einer berechneten Zahl wird direkt ein Farbwert zugeordnet. Somit lässt sich leicht eine Farbverschiebung programmieren, die sich auf Englisch color-cycle nennt.

Wenn wir ein IndexColorModel verwenden wollen, geben wir im Konstruktor eine Anzahl Bits pro Pixel zusammen mit einer Tabelle an, die die Komponenten Rot, Grün und Blau sowie optional die Transparenzen enthält. Die Farbtabelle, die über einen Index die Farbe verrät, kann maximal 256 Farben aufnehmen. Dies ist leider eine Einschränkung, beschränkt aber den Speicher, da nur ein byte an Stelle eines short belegt wird.

Abbildung
Hier klicken, um das Bild zu Vergrößern


class java.awt.image.IndexColorModel
extends ColorModel

gp IndexColorModel( int bits, int size,
byte r[], byte g[], byte b[], byte a[] )
gp IndexColorModel( int bits,int size,
byte r[], byte g[], byte b[], int trans )
gp IndexColorModel( int bits, int size
byte r[], byte g[], byte b[] )
gp IndexColorModel( int bits, int size, byte cmap[],
int start, boolean hasalpha, int trans )
gp IndexColorModel( int bits, int size, byte cmap[],
int start, boolean hasalpha )
gp IndexColorModel( int bits, int size, int cmap[],
int start,boolean hasalpha, int trans,
int transferType )

An den Konstruktoren lässt sich ablesen, dass mehrere Wege gegangen werden können. Die Farben können als Einzelfelder einem IndexColorModel übergeben werden oder als zusammengepacktes Feld. Dann erfolgt die Speicherung nach dem Standard-RGB-Modell. Vorsicht ist bei einem Alpha-Wert geboten. Dieser folgt nach dem Blauton. So ist die Reihenfolge bei Transparenz 0xRRGGBBAA. Das ist sehr verwirrend, da wir es gewohnt sind, den Alpha-Wert vor dem Rotwert zu setzen.

Intern werden die Werte in einem Feld gehalten. Der erste Wert gibt die Anzahl der Bits an, die einen Pixel beschreiben. Er darf acht Bit nicht überschreiten, da die Längenbeschränkung 2^8 = 256 maximale Farben vorgibt. Der nächste Wert size ist die Größe der Tabelle. Sie sollte mindestens 2^bits groß sein. Andernfalls werden Farben fehlerhaft zugeordnet. Präziser heißt dies, dass sie Null sind, da ja der new-Operator das Feld automatisch mit Null-Werten belegt. Sind in der Farbtabelle Apha-Werte abgelegt, dann sollte hasalpha den Wert true annehmen. Sind alle Werte in einer Tabelle, berechnet sich der Farbwert zu einem Index wie folgt: Betrachten wir keinen Alpha-Wert und unser Pixel hat den Wert f(arbe),

gp dann ist der Rotwert an der Stelle colorMap[start+3*f] und
gp der Grünwert an der Stelle colorMap[start+3*f+1] und
gp der Blauwert schließlich bei colorMap[start+3*f+2].

Um Informationen über die internen Werte und die Größe der Tabelle zu erhalten, reicht ein toString(). Die Größe der Tabelle liefert die Methode getMapSize().

Mit den finalen Methoden getReds(byte redArray[]), getGreens(byte greenArray[]), getBlues(byte blueArray[]) und getAlphas(byte alphaArray[]), deren Rückgabewert void ist, lassen sich die Farbinformationen auslesen und als Ergebnis in das Feld legen. Die Felder müssen schon die passende Größe haben, die sich jedoch mit final int getMapSize() erfragen lässt. Die Methode getTransparentPixel() liefert den Index des transparenten Pixels. Gibt es keinen, ist der Wert -1.

Werfen wir zur Demonstration noch einen Blick auf die Methode getGreens(). Wir sehen deutlich, dass das Feld eine passende Größe haben muss.

final public void getGreens(byte g[]) {
  for (int i = 0; i < map_size; i++)
    g[i] = (byte) (rgb[i] >> 8);
}

An getRed() sehen wir ebenso, dass der Pixel auch direkt ein Index für das private Feld rgb ist. Wenn der Index über die Feldgröße läuft, müssen wir den Fehler selbst behandeln.

final public int getRed(int pixel) {
  return (rgb[pixel] >> 16) & 0xff;
}

Wenden wir unsere Aufmerksamkeit auf ein Programm, welches ein Bytefeld erzeugt und aus sechs Farben die Pixel in das Feld schreibt. Zum Schluss konvertieren wir das Bytefeld mit einem MemoryImageSource in ein Image-Objekt. Für diese Klasse können wir ein IndexColorModel angeben, das dann folgendes Format hat:

ColorModel cm = IndexColorModel( 8, colorCnt, r, g, b );

Hier handelt es sich um ein Farbmodell mit acht Bits und sechs Farben. Die folgenden Werte zeigen auf die drei Felder mit den Farbwerten. Anschließend erzeugt createImage() mit diesem Farbmodell das Image-Objekt.

Image i = createImage( new MemoryImageSource(w,h,cm,pixels,0,w) );

Listing 14.22 IndexColorModelDemo.java

import java.awt.*;
import java.awt.image.*;
public class IndexColorModelDemo extends Frame
{
  Image i;
  static int w = 400, h = 400;
  int pixels[] = new int [w*h];
  Color colors[] = {
      Color.red, Color.orange, Color.yellow,
      Color.green, Color.blue, Color.magenta
  };
  IndexColorModelDemo()
  {
    int colorCnt = colors.length;
    byte r[] = new byte[colorCnt],
         g[] = new byte[colorCnt],
         b[] = new byte[colorCnt];
    for ( int i = 0; i < colorCnt; i++ )
    {
      r[i] = (byte) colors[i].getRed();
      g[i] = (byte) colors[i].getGreen();
      b[i] = (byte) colors[i].getBlue();
    }
    int index = 0;
    for ( int y = 0; y < h; y++ )
      for ( int x = 0; x < w; x++ )
        pixels[index++] = (int)(Math.random() * colorCnt);
    i = createImage( new MemoryImageSource( w, h,
        new IndexColorModel(8, colorCnt, r, g, b),
        pixels, 0, w) );
  }
  public void paint( Graphics g )
  {
    if ( i != null )
      g.drawImage( i, 0, 0, this );
  }
  public static void main( String args[] )
  {
    IndexColorModelDemo d = new IndexColorModelDemo();
    d.setSize( w, h );
    d.show();
  }
}





1 Hört sich an wie ein Fußballverein, ist aber keiner.





Copyright (c) Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de