Wie können in Java Bilddateien geladen werden?

Das Laden von externen Ressourcen ist abhängig von der verwendeten Fensterbibliothek. Abhängig davon, ob die GUI-Programmierung mit AWT, Swing oder JavaFX erfolgt, gibt es auch Unterschiede beim Laden von Graphikdateien.
Der vorliegende Artikel erläutert die Verfahrensweisen unter Swing und JavaFX.

Das Ausgangsformat für die überwiegende Arbeit mit Bildern unter AWT und Swing ist das java.awt.image.BufferedImage, das die Klasse java.awt.Image erweitert. Ausgehend von diesem Typ kann ein Bild dann je nach Verwendungszweck in weitere Bildtypen wie ImageIcon, RenderedImage, etc. gewandelt werden.
Bei der Verwendung von JavaFX ist die entsprechende Klasse javafx.scene.image.Image, von der als Spezialisierung das WritableImage abgeleitet ist.

Vielfach sorgt jedoch die Methode für Verwirrung, mit der eine Graphikdatei in einem Programm geladen werden kann. Nicht zuletzt deshalb, weil sich die Verfahren nicht nur nach verwendeter GUI-Bibliothek, sondern auch nach Programmtyp (Applet, Application) unterscheiden.
Hier sollen die Verfahren für Java-Desktop-Applikationen mit Swing und JavaFX dargestellt werden. Voraussetzung sind grundlegende Kenntnisse über die Angabe von Pfaden.

Swing

Das Demo-Programm

Um die verschiedenen Lademethoden zu demonstrieren dient eine einfache GUI, die innerhalb eines JFrame und eines JPanel ein JLabel lädt, auf dem das geladene Bild als ImageIcon dargestellt wird. Das Laden selbst und die Konvertierung des BufferedImage in ein ImageIcon geschehen in der Methode showImg(). Sie wird im Konstruktor des Programms bei der Erzeugung des Label-Objektes aufgerufen und gibt das ImageIcon-Objekt oder im Fehlerfall null zurück.
Das Wandeln des BufferedImage in ein ImageIcon erfolgt durch dessen Übergabe an den Konstruktor des ImageIcon.

Die im Folgenden aufgezeigten Routinen zum Laden des Bildes müssen an Stelle des Kommentars /* Hier Graphikdatei laden */ eingefügt werden. Die zugehörigen import-Anweisungen sind bereits vorhanden.

package grafik;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class BildLaden extends JFrame {

    public BildLaden() {
        JLabel label = new JLabel(showImg());
        JPanel panel = new JPanel();
        panel.add(label);
        this.add(panel);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setTitle("Bild laden bei Swing");
        this.setSize(600, 400);
        this.setLocationRelativeTo(null);
        this.setVisible(true);
    }

    private ImageIcon showImg() {
        BufferedImage img = null;
        try {
           /* hier Graphikdatei laden */
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ImageIcon(img);
    }

    public static void main(String[] args) {
        new BildLaden();
    }
}

Bilddatei im Klassenpfad

Der Klassenpfad oder Classpath umfasst eine Liste von Dateipfaden, in denen die Werkzeuge des JDK nach zu verwendenden *.class-Dateien suchen. Alle Verzeichnisse und Dateien, die sich in Eclipse innerhalb des src-Verzeichnisses eines Projektes befinden, sind in den Klassenpfad eingeschlossen.1 Das gilt im vorliegenden Fall auch für das Verzeichnis img und dessen Inhalt.

Die ladende Datei BildLaden.java liegt im Package grafik, das sich auf gleicher Ebene wie img innerhalb des Klassenpfades befindet.
Eine Bilddatei kann in diesem Fall auf die folgenden Weisen geladen werden:

img = ImageIO.read(getClass().getResource("../img/test.jpg"));

Das Laden erfolgt bei diesem Verfahren im Hintergrund mit Hilfe des sog. ClassLoaders des aktuellen Objektes, an den die Anfrage delegiert wird.
Die Grundlage stellt hier die mehrfach überladene statische Methode read() der Klasse ImageIO dar. Sie liefert ein BufferedImage und bekommt im vorliegenden Beispiel ein URL-Objekt als Parameter übergeben. Dies wird von der Methode Class.getRessource() zurückgegeben. Sie verwendet hier als Parameter den String eines relativen Pfades. Er muss, wie hier geschehen, ausgehend von der ladenden Datei angegeben werden. Package-Bestandteile werden hierbei wie Verzeichnisse behandelt.

Wahlweise kann auch ein absoluter Pfad bezogen auf das src-Verzeichnis des Projektes angegeben werden. Er muss dann mit einem Schrägstrich ('/') beginnen.

img = ImageIO.read(getClass().getResource("/img/test.jpg"));

Bilddatei außerhalb des Klassenpfades

Befindet sich eine zu ladende Datei nicht im Klassenpfad, so muss ein anderes Verfahren angewandt werden.

Im vorliegenden Fall liegt das Verzeichnis img mit der Bilddatei auf gleicher Ebene wie das Verzeichnis src. Um eine Bilddatei zu laden, kann hier kein ClassLoader verwendet werden, da sich das Verzeichnis außerhalb von dessen Zugriffsbereich befindet. Statt dessen wird der Methode ImageIO.read() ein File-Objekt als Parameter übergeben:

img = ImageIO.read(new File("img/test.jpg"));

Zur Erzeugung akzeptiert ein Konstruktor der Klasse File den Pfad zur Bilddatei als String. Dies kann auf zwei Weisen erfolgen: Der Pfad kann absolut bezogen auf das Dateisystem oder aber, wie hier demonstriert, relativ zum Eclipse-Projektordner angegeben werden (diesmal jedoch ohne führenden Schrägstrich!). Letzteres funktioniert deshalb, weil Eclipse das Projektverzeichnis (nicht src oder bin!) als Arbeitsverzeichnis verwendet.

Bilddatei in externer JAR.

Nicht selten werden Ressourcen eines Programms in externen JAR-Dateien gespeichert, aus denen sie dann geladen werden können. Der Zugriff auf eine derartige Datei ist nicht direkt durch die Klasse ImageIO möglich, sondern muss vorher über eine JarURLConnection durch einen InputStream erfolgen, der dann weiterverarbeitet werden kann.

Bei diesem Beispiel befindet sich die JAR-Datei mit der Graphikdatei im Verzeichnis img, das wiederum nicht im Klassenpfad liegt, sondern auf der Ebene, auf der sich auch src befindet. Die Bilddatei test.jpg ist auf oberster Ebene innerhalb von img.jar lokalisiert.

Der JarURLConnection muss eine URL übergeben werden, zu der die Verbindung geöffnet wird. Hierbei muss man insbesondere die Syntax der URL beachten. Der Pfad zur JAR-Datei selbst kann innerhalb des Projektes wiederum relativ zu diesem oder aber absolut zum Dateisystem des Rechners angegeben werden. Dem Pfad muss die Formel jar:file: als Protokoll vorangestellt werden. Nach der Extension jar müssen ein Ausrufezeichen und ein Schrägstrich ('!/') stehen. Danach folgt der Pfad zur Bilddatei innerhalb des JAR-Archivs.

URL url = new URL("jar:file:img/img.jar!/test.jpg");
JarURLConnection jarConnection = (JarURLConnection) url.openConnection();
jarConnection.setDoOutput(true);
InputStream inputStream = jarConnection.getInputStream();
img = ImageIO.read(inputStream);
inputStream.close();

Da die Verbindung im vorliegenden Fall für eine Ausgabe genutzt werden soll, ist es sinnvoll, den boolschen Wert für URLConnection.doOutput auf true zu setzen, bevor von der Verbindung ein InputStream abgeleitet wird. Dieser kann schließlich wiederum per ImageIO.read() ausgelesen werden.

JavaFX

JavaFX ist zunehmend dabei, Swing als Desktop-Fenster-Bibliothek zu ersetzen, da sich die GUI-Programmierung mit JavaFX u.a. dank der Möglichkeiten der Verwendung von Style-Sheets als hochflexibel erweist. Dies ist selbstverständlich auch mit Änderungen in anderen Bereichen verbunden, z.B. bezüglich des Ladens von Graphiken.

Der wesentliche Unterschied zu Swing ist die Kompensation der Klasse JavaIO, die ja zum Swing-Package javax.imageio gehört. Die Funktionen der überladenen Methoden read() werden dabei weitgehend durch die Konstruktoren der Klasse javafx.scene.image.Image übernommen.
Während die Methoden ImageIO.read() als Parameter entweder Objekte vom Typ InputStream, File oder URL übergeben bekommen und Objekte vom Typ BufferedImage zurückgeben, werden bei JavaFX Objekte vom Typ javafx.scene.image.Image direkt erzeugt. Den Konstruktoren werden zu diesem Zweck nur noch ein InputStream oder die String-Repräsentation einer URL, nicht das URL-Objekt selbst, übergeben. Die Möglichkeit, ein File-Objekt zu verwenden ist nicht mehr vorgesehen.

Das Demo-Programm

Das Demo-Programm zeigt, wie eine Graphikdatei geladen und durch eine ImageView direkt dargestellt werden kann. Die u.a. Lademethoden müssen in dem Bereich eingefügt werden, der mit /* hier Graphikdatei laden */ gekennzeichnet ist.

Als Layout-Manager wird hier ein BorderPane als Node erzeugt, der der Scene übergeben wird. Zur Demonstration der Skalierungsmöglichkeiten eines ImageView ist die Scene auf die Größe von 250x150 Pixel gesetzt.

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class BildLadenFX extends Application {

    private static final int IMG_HEIGHT = 150;
    private static final int IMG_WIDTH = 250;

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setTitle("Bild laden bei JavaFX");
        Image img = null;
        BorderPane root = new BorderPane();
        Scene scene = new Scene(root, IMG_WIDTH, IMG_HEIGHT);
        /* hier Graphikdatei laden */
        ImageView imgView = new ImageView(img);
        imgView.setFitHeight(IMG_HEIGHT);
        imgView.setPreserveRatio(true);
        imgView.setSmooth(true);
        root.getChildren().add(imgView);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

Zunächst wird über den jeweiligen Konstruktor ein Image-Objekt erstellt, das wiederum zur Erzeugung eines ImageView herangezogen wird. Die Klasse stellt eine Reihe von Methoden bereit, die der Manipulation der Art der Darstellung eines Image-Objektes dienen: Nach der Skalierung unter Beibehaltung der Proportionen, sowie dem Setzen eines Flags, das die Qualität des Bildrenderings gegenüber der Geschwindigkeit bevorzugt, wird das ImageView dem Scenegraph hinzugefügt.

Bilddatei mit URL laden

Wie unter Swing auch, so kann innerhalb eines Eclipse-Projektes eine URL verwendet werden, um mittels Classloader eine Graphikdatei zu laden. Wie oben erwähnt, muss hier jedoch nicht das URL-Objekt selbst, sondern dessen String-Repräsentation verwendet werden. Sie wird durch die Methode toExternalForm() der Klasse URL zurückgegeben.
Der Dateipfad entspricht im vorliegenden Fall wiederum dem Schema von Bilddateien innerhalb des Klassenpfades.

img = new Image(getClass().getResource("/img/test.jpg").toExternalForm());

Auch außerhalb des Klassenpfades können nun Bilder mittels eines übergebenen URL-Strings geladen werden. Lokalen Pfaden muss hierzu das Protokoll file: vorangestellt werden.

img = new Image("file:/Users/joecze/img/test.jpg");

Dateien im Netzwerk werden natürlich auf eine äquivalente Weise referenziert:

img = new Image("http://javabeginners.de/img/javabeginners_240.png");

1) Genau genommen gehört bei getrennter Lagerung der .java- und .class-Dateien nicht das Verzeichnis src sondern bin zum Klassenpfad. Durch den Eclipse-eigenen Build-Mechanismus werden Ressourcen jedoch von src nach bin kopiert.