12.7Grafiken
Bilder sind neben dem Text das wichtigste visuelle Gestaltungsmittel. In Java können Grafiken an verschiedenen Stellen eingebunden werden, so zum Beispiel als Grafiken in Zeichengebieten (Canvas) oder als Icons in Schaltflächen, die angeklickt werden. Über Java können problemlos GIF-, PNG- und JPEG-Bilder geladen werden.
JavaFX bietet ein eigenes Paket javafx.scene.image mit diversen Typen rund um Bilder.
[»]Hinweis
Das GIF-Format (Graphics Interchange Format) ist ein komprimierendes Verfahren, das 1987 von CompuServe-Betreibern zum Austausch von Bildern entwickelt wurde. GIF-Bilder können bis zu 1.600 × 1.600 Punkte umfassen. Die Komprimierung nach einem veränderten LZW[ 109 ](Benannt nach den Erfindern Lempel, Ziv und Welch.)-Packverfahren hat keinen Einfluss auf die Bildqualität (sie ist verlustfrei). Jedes GIF-Bild kann aus maximal 256 Farben bestehen – bei einer Palette aus 16,7 Millionen Farben. Entsprechend dem Standard von 1989 können mehrere GIF-Bilder in einer Datei gespeichert werden. JPEG-Bilder sind dagegen in der Regel verlustbehaftet, und das Komprimierungsverfahren speichert die Bilder mit einer 24-Bit-Farbpalette. Der Komprimierungsfaktor kann prozentual eingestellt werden.
12.7.1Klasse Image
JavaFX repräsentiert Grafiken als javafx.scene.image.Image-Objekte. Über den Konstruktor der Image-Klasse wird eine Quelle angegeben, woher die Daten für das Bild kommen. Die beiden populären Konstruktoren sind:
Image(InputStream is)
Image(String url)
Ist die Grafik geladen, können Eigenschaften wie Höhe und Breite über Getter und auch über JavaFX-Properties erfragt werden.
Die Klasse Image repräsentiert das Bild, aber es ist kein Knoten für den Szenegraphen. Image-Objekte werden in JavaFX an unterschiedlicher Stelle eingesetzt:
als Grafik für die Fensterdekoration
als Vorgabe für den Maus-Cursor (javafx.scene.ImageCursor)
als Bild, das auf eine Zeichenfläche (javafx.scene.canvas.Canvas) gemalt wird
bei Effekten
als Objekt, wenn eine Grafik im der Zwischenablage ist
[»]Hinweis
Die Klasse Image hat in JavaFX zwei Funktionen: Einmal lädt sie Bilder, und einmal repräsentiert sie Bilder. Das ist in AWT/Swing anders, hier gibt es mit Image eine Bildrepräsentation, aber das Laden/Speichern übernehmen andere Klassen, etwa ImageIO.
12.7.2ImageView
Um eine Grafik in den Szenegraphen zu hängen, wird sie in eine ImageView verpackt; ImageView ist eine direkte Unterklasse von Node. Zum Aufbau gibt es drei Konstruktoren:
ImageView()
ImageView(Image image)
ImageView(String url)
Die Grafik kann mit den JavaFX-Properties fitHeight und fitWidth (beide DoubleProperty) in eine Box gepresst werden. Eine Skalierung geschieht automatisch, und die BooleanProperty preserveRatio bestimmt, ob das Verhältnis von Breite zu Höhe gleich bleibt. Eine weitere BooleanProperty smooth sagt, ob eine bessere oder schnellere Variante zur Berechnung einer skalierten Version verwendet werden soll. Mit einem Viewport lässt sich ein Teil der Grafik zeigen.
[zB]Beispiel
Erzeuge eine ImageView mit einer Grafik image. Zeige von der Grafik einen Ausschnitt (Viewport), und skaliere die Grafik auf 200 Pixel in der Höhe, wobei sich die Breite im Verhältnis mit ändern soll. Die Skalierung soll eine gute Qualität haben und das Ergebnis aus Performance-Gründen im Cache gehalten werden.
Rectangle2D viewportRect = new Rectangle2D( 10, 10, 80, 80 );
node.setViewport( viewportRect );
node.setFitHeight( 200 );
node.setPreserveRatio( true );
node.setSmooth( true );
node.setCache( true );
Icon-Sammlungen *
Wer für seine grafischen Oberflächen Icons einsetzt, der findet beim Tango Desktop Projekt (http://tango.freedesktop.org/) viele Standard-Icons in den Auflösungen 16 × 16, 22 × 22, 32 × 32 und ebenso im SVG-Format. Die Website http://www.iconfinder.net/ bietet eine Suche nach bestimmten Begriffen und findet freie Icons nach weiteren Kriterien wie Hintergrundfarbe/Transparenz, Größe wie auch kommerziell nutzbare Icons. Crystal Clear (http://commons.wikimedia.org/wiki/Crystal_Clear) steht unter der Lizenzform LGPL und ist damit auch für kommerzielle Anwendungen nutzbar.
12.7.3Programm-Icon/Fenster-Icon setzen
Zumindest unter Windows ist jedem Fenster ein kleines Bildchen zugeordnet, das ganz links in der Titelzeile untergebracht ist. Das Programm-Icon lässt sich in JavaFX über das Stage-Objekt ändern; wir erinnern uns, dieses Objekt wird in der JavaFX-Startmethode start(Stage stage) übergeben. Die Stage hat assoziierte Icons, die mit getIcons() erfragt werden, das Ergebnis ist vom Typ ObservableList<Image>. Interessant ist die Tatsache, dass es nicht nur ein Icon geben kann, sondern mehrere. Eine Grafik der Größe 16 × 16 Pixel ist sinnvoll, doch auch größere Größen sind angebracht, weil das Icon auch beim Minimieren der Anwendung gezeigt wird. Jedes Icon wird aber immer auf die notwendige Größe skaliert.
[zB]Beispiel
Setze ein Programm-Icon:
stage.getIcons().add( programIcon );
12.7.4Zugriff auf die Pixel und neue Pixel-Bilder *
Die Klasse Image hat keine Methode zum Abfragen und Setzen eines Farbwerts an einer bestimmten Koordinate, das ist ausgelagert in die Extratypen PixelReader und PixelWriter.
Klasse PixelReader
Ein Image-Objekt liefert mit getPixelReader() diesen PixelReader, und dann sind zum Beispiel mit int getArgb(int x, int y) und Color getColor(int x, int y) Abfragen möglich oder auch ganze Bereichsanfragen mit Feld-Rückgabe.
[zB]Beispiel
Lies die Farbwerte eines Image-Objekts image, und zerlege die Rückgabe in die Farbwerte Rot, Grün, Blau und den Alpha-Wert:
int alpha = (argb >> 24) & 0xff;
int red = (argb >> 16) & 0xff;
int green = (argb >> 8) & 0xff;
int blue = (argb) & 0xff;
Die Methode getArgb(…) liefert als Rückgabe einen Wert im Standard-RGB-Modell – unabhängig von der tatsächlichen physikalischen Kodierung – und im Standard-RGB-Farbraum. Sind nur die rohen Farbwerte nötig, würde auch getColor(…) zum Ziel führen, doch hier hätten wir es immer mit einem Zwischenobjekt zu tun, was bei vielen Abfragen nicht so optimal ist.
Ausblick
Grafiken müssen im Speicher repräsentiert werden und dafür gibt es ein Speichermodell. Anschaulich gesagt drückt es aus, wie im Speicher die Farbinformationen eines Bildes abgelegt sind, ob etwa je 8 Bit für Rot/Grün/Blau oder ob, wie bei GIF, ein Index und eine Farbtabelle verwendet werden. JavaFX ist bei den Farb- und Speichermodellen flexibel und abstrahiert in einen Typ javafx.scene.image.PixelFormat.
WritableImage und PixelWriter
Nicht immer kommen die Bilder vom Datensystem oder aus dem Internet. Mit der JavaFX-Bibliothek lassen sich einfach neue Grafiken anlegen und die Pixel setzen. Dazu bietet JavaFX eine Unterklasse von Image, und zwar WritableImage. Während Image selbst immutable ist, erlaubt WritableImage eine Veränderung der Pixel.
Da WritableImage von Image erbt, erbt es auch alle Eigenschaften und Properties, wie etwa getPixelReader(). Es kommen nur drei Konstruktoren und eine Methode hinzu, die Klasse ist also schlank:
extends Image
WritableImage(int width, int height)
WritableImage(PixelReader reader, int width, int height)
WritableImage(PixelReader reader, int x, int y, int width, int height)
Initialisiert ein WritableImage mit einer Größe. Ein anderes Bild kann von einem PixelReader übernommen werden, es entsteht also eine Kopie.PixelWriter getPixelWriter()
Liefert einen PixelWriter, der schreibenden Zugriff auf die Pixel des Bildes ermöglicht.
Bei WritableImage finden wir die Methode getPixelWriter(), die symmetrisch ist zum PixelReader und entsprechende getXXX(…)-Methode bietet.
Ein Beispiel zum Lesen und Schreiben von Pixeln
Das folgende Programm lädt ein Bild und setzt einen Bewegungssensor an die ImageView, in der ein WritableImage eingebettet ist. Bewegt der Nutzer den Mauszeiger über das Bild, erfragen wir den Farbwert unter der Koordinate und lassen uns über die Color-Klasse einen invertierten Farbton geben, womit wir den vorherigen Farbwert für das Pixel überschreiben:
Listing 12.11com/tutego/insel/javafx/WritableImageDemo.java, getImageView()
try ( InputStream stream = getClass().getResourceAsStream( filename ) ) {
if ( stream == null )
return new ImageView();
Image img1 = new Image( stream );
final WritableImage img2 = new WritableImage( img1.getPixelReader(),
(int) img1.getWidth(), (int) img1.getHeight() );
ImageView imageView = new ImageView( img2 );
imageView.setOnMouseMoved( new EventHandler<MouseEvent>() {
@Override public void handle( MouseEvent e ) {
Color c = img2.getPixelReader().getColor( (int) e.getX(), (int) e.getY() );
img2.getPixelWriter().setColor( (int) e.getX(), (int) e.getY(), c.invert() );
}
} );
return imageView;
}
catch ( IOException e ) { // Exception von close()
throw new UncheckedIOException( e );
}
}
Abbildung 12.10Screenshot der Anwendung WritableImageDemo