Grafikdateien laden und anzeigen (Java)

Aus Byte-Welt Wiki
Version vom 27. Februar 2014, 14:55 Uhr von L-ectron-x (Diskussion | Beiträge) (Kleiner Pfad-Exkurs)
Zur Navigation springenZur Suche springen

Häufig möchte man aus verschiedenen Gründen Grafikdateien, wie Icons, Eyecatcher oder Fotos in Java-Programme einbinden. Im Folgenden soll anhand von Code-Beispielen gezeigt werden, wie unter verschiedenen Bedingungen und GUI-Schnittstellen (AWT/Swing) Grafiken eingebunden werden können.
Dieses Tutorial versucht sämtliche Möglichkeiten und Sachverhalte vom Laden von Grafikdateien in Java näher zu beleuchten und geht dabei auch die Unterschiede zwischen AWT und Swing ein.

Kleiner Pfad-Exkurs

Das häufigste Problem, das von Einsteigern beschrieben wird, ist das Laden von Bildern aus verschiedensten Quellen und da besonders, die Einbindung über die URL oder den Pfad zum Bild. Denn oft möchte man ein Bild laden, welches sich nicht im gleichen Verzeichnis wie die Bytecode-Datei befindet. Die folgende kurze Einführung zeigt die Verwendung und die Schreibweisen der Angabe des Pfades zur Referenzierung von Grafikdateien.

Relative Pfadangaben

Bilder im Verzeichnis der einbettenden Klasse

bild.png oder ./bild.png

Bilder eine Verzeichnisebene tiefer

inhalt/bild.png

zwei Ebenen tiefer: inhalt/bilder/bild.png

Bilder eine Verzeichnisebene höher

../bild.png

zwei Ebenen höher: ../../bild.png

Absolute Pfadangaben

Beispiel Windows

c:\users\byte-welt\pictures\bild.png

Beispiel Linux

/home/byte-welt/pictures/bild.png

Laden von Grafikdateien im AWT

Im AWT, welches die Standard-Schnittstelle für die Entwicklung grafischer Benutzeroberflächen bis zur Veröffentlichung von Java 1.2 war, wird das Einlesen von Grafikdaten in Applets und Applikationen unterschiedlich gehandhabt. Bis auf den gleichen Namen der benutzten Methode zum Einlesen, müssen hier verschiedene Konzepte angewendet werden. Das führt oft zu Verwirrung und Vorgehensweisen, die unnötig sind.

Applets

Laden von Bildern in Applets

Da Applets auf einem Webserver gespeichert sind und nach dem Laden in den Browser externe Resourcen wie Bilder ebenfalls aus dem Internet nachladen, werden die URLs (Quellenanzeiger) der Resourcen meist ausgehend vom URL des Applets angegeben. Das heißt: getCodeBase() liefert den URL des Applets im Internet bzw. Netzwerk. Und davon ausgehend kann nun der exakte Pfad zu unserem Bild angegeben werden.

Beispiele: <code=java> Image image1 = getImage(getCodeBase(),"Bild1.jpg"); //Das Bild liegt hier im gleichen Verzeichnis, wie das Applet.

Image image2 = getImage(getCodeBase(),"../Bild2.jpg"); //Das Bild liegt hier im Verzeichnis über dem Applet.

Image image3 = getImage(getCodeBase(),"bilder/Bild3.jpg"); //Das Bild liegt hier vom Applet ausgehend in einem Unterverzeichnis bilder.

Image image4 = getImage(new URL("http://www.meine-domain.de/bilder/Bild3.jpg")); //Nicht erlaubt! Wirft eine AccessControlException. </code=java> Beim Programmieren sollte darauf geachtet werden, dass generell Slashes (also /) als Pfadseparatoren verwendet werden. Auch unter Windows! Sonst müsste jeder Pfadseparator escaped werden, was wieder zu einer Plattformabhängigkeit führt.

Einfache Applet-Klasse

Die einfachste Variante, eine Grafik in ein Applet (AWT) einzubinden ist, sie von der URL einzulesen und in der paint()-Methode direkt auf die Appletfläche zu zeichnen.

<code=java>import java.awt.*; import java.applet.*;

public class PictureApplet extends Applet {

  private Image image;
  public void init() {
     image = getImage(getCodeBase(),"Bild.jpg");
  }
  public void paint(Graphics g) {
     super.paint(g);
     if(image != null) {
        g.drawImage(image, 0, 0, this);
     }
  }

}</code=java>

Diese Variante ist aber wenig effizient und häufig kommt es vor, dass kein Bild ausgegeben wird, obwohl der URL und der Pfad zum Bild stimmen. Das Einlesen des Bildes dauert hier bei größeren und/oder mehreren Dateien etwas länger, so dass die paint()-Methode schon ausgeführt wird, obwohl das Laden des Bildes noch nicht abgeschlossen ist. In der Java-Konsole würde in diesem Fall eine NullPointerException ausgegeben werden, wenn image nicht auf null geprüft werden würde. Wegen der Prüfung, stürzt das Programm in dem Fall nicht ab, es zeichnet aber u. U. auch kein Bild. Um sicherzustellen, dass das Bild fertig geladen ist, bevor die Zeichenroutine angestoßen wird, wird der Einsatz der Klasse MediaTracker empfohlen.

Einsatz des MediaTrackers

Um sicher zu stellen, dass ein Bild erst dann auf eine AWT-Anwendung gezeichnet wird, wenn es wirklich vollständig geladen wurde, bedient man sich der Klasse java.awt.MediaTracker, welche das Laden der Dateien in einem extra Thread überwacht und die Kontrolle ans Programm zurückgibt, wenn die Bilder geladen wurden.

Beispiel: <code=java>import java.awt.*; import java.applet.*;

public class PictureApplet extends Applet {

  private Image image;
  private MediaTracker tracker;
  public void init() {
     tracker = new MediaTracker(this);
     image = getImage(getCodeBase(),"Bild.jpg"); //Laden des Bildes anstoßen
     tracker.addImage(image, 0); //Image an den MediaTracker zur Überwachung übergeben
     try {
        tracker.waitForAll(); //auf alle zu ladenden Dateien warten
     }
     catch (InterruptedException e) {
        e.printStackTrace(); //Fehlerausgabe auf der Java-Konsole
     }
  }
  public void paint(Graphics g) {
     super.paint(g);
     if(image != null) {
        g.drawImage(image, 0, 0, this);
     }
  }

}</code=java> Auch hier, liegt das Bild im gleichen Verzeichnis, wie das Applet.
Die Vorgehensweise bei der Benutzung von MediaTracker in Applikationen ist die gleiche, nur dass Bilder dort mit der getImage()-Methode aus java.awt.Toolkit geladen werden.

AWT-Applikationen

Laden von Bildern für AWT-Applikationen

Anwendungen, welche das AWT als GUI-Schnittstelle anwenden, greifen auf die Klasse java.awt.Toolkit zurück, um Bilder zu laden <code=java> Image image = Toolkit.getDefaultToolkit().getImage("picture.jpg"); </code=java> Genau wie bei Applets muss hier damit gerechnet werden, dass u. U. das Bilder nicht gezeichnet wird, obwohl der Pfad zum Bild stimmt. Stattdessen erhält man eine NullPointerException. Auch hier empfiehlt sich zusätzlich der Einsatz von MediaTracker, um sicherzustellen, dass das Bild im Speicher ist, bevor die Zeichenroutine angestoßen wird.

Ausgabe von Bildern in GUI-Komponenten

Die meisten GUI-Komponenten des AWT haben von Hause aus keine Möglichkeiten mitbekommen, um Bilder und Grafiken anzuzeigen. Diese Fähigkeiten müssen ihnen nachträglich vom Programmierer verliehen werden. Dazu ist es erforderlich, vom gewünschten Objekt-Typ zu erben und Methoden zur Ausgabe einzufügen. Im Folgenden werden wir das am Beispiel eines java.awt.Panel demonstrieren. <code=java> public class ImagePanel extends Panel {

  private Image image;
  public ImagePanel(Image image) {
     setImage(image);
  }
  public void setImage(Image image) {
     this.image = image;
     setPreferredSize(new Dimension(image.getWidth(this), image.getHeight(this)));
     repaint();
  }
  public void paint(Graphics g) {
     super.paint(g);
     if(image != null) {
        g.drawImage(image, 0, 0, this);
     }
  }

} </code=java> Dieses Panel kann in AWT-Anwendungen nun Bilder anzeigen. LayoutManager berechnen auch den Platz, den das Label im Layout benötigen wird.

Laden von Grafikdateien in Swing

In Swing haben die Java-Erfinder von Sun sehr leistungsfähige Klassen für das Einlesen von Grafikdaten aus unterschiedlichen Quellen entwickelt, die auch die Anwendung in Applets und Applikationen vereinheitlicht.

Varianten zum Einbinden von Grafiken

Viele Wege führen bekanntlich nach Rom, so auch beim erfolgreichen Laden und Anzeigen von Grafikdateien in Java.

ImageIO

javax.imageio.ImageIO ist eine sehr effiziente und einfach zu handhabende Klasse zum Laden von Grafikdateien.
Sie stellt u.a. Methoden bereit, um Bilder aus verschiedenen Quellen zu laden. So z.B. aus einer Datei, einem InputStream oder von einer URL. Ergebnis des Einlesens mit ImageIO ist immer ein Objekt vom Typ BufferedImage, welches die Klasse java.awt.Image erweitert.


Laden aus einer Datei mit Angabe ihres Namens:
<code=java>Image image = ImageIO.read(new File("demo.jpg"));</code=java> Diese Variante ist für das Laden von Bildern aus Dateien des lokalen Dateisystems geeignet. Z.B. mit Hilfe von JFileChooser
Sie kommt aber nicht für die Verwendung von Applets in Frage, weil mit File-Zugriffen auf dem Client unerlaubte Operationen versucht werden würden. Sie ist also nur für den Gebrauch in Applikationen denkbar.


Laden eines Bildes aus einem InputStream:
<code=java>Image image = ImageIO.read(getClass().getResource("demo.jpg"));</code=java> getClass().getResource("...") erzeugt hier einen InputStream zur Datei. Diese Variante kommt bspw. zum Einsatz, wenn sich Bilder innerhalb einer Jar-Datei befinden. Sie funktioniert aber auch, wenn die Klassen und Grafikdateien noch unverpackt im lokalen Dateisystem liegen. Man braucht also nichts mehr verändern, wenn man später aus den fertigen Klassen eine Jar-Datei erzeugt.


Laden eines Bildes in einem Package relativ zu dieser Klasse aus einem InputStream:
<code=java>Image image = ImageIO.read(getClass().getResource("../images/demo.jpg"));</code=java> Es wird ein InputStream zur Datei in einer Jar-Datei erzeugt, der sie aus einem Verzeichnis relativ zu dieser Klasse läd.


Laden eines Bildes in einem Package absolut zu einer Klasse aus einem InputStream:
<code=java>Image image = ImageIO.read(getClass().getResource("/de/any-domain/project/images/demo.jpg"));</code=java> Es wird ein InputStream zur Datei erzeugt, der ausgehend vom obersten Package ganz genau den Speicherort in einer Jar-Datei spezifiziert.


Laden eines Bildes von einer URL:
<code=java>Image image = ImageIO.read(new URL("http://www.any-domain.de/images/demo.jpg"));</code=java> Diese Variante kommt zum Einsatz, wenn Bilder aus einem Netzwerk geladen werden sollen.


Weil dabei auch etwas schief gehen kann, muss der Abschnitt jeweils in einem try-catch - Block eingebaut werden.

ImageIO wird meistens dann benutzt, wenn man Grafikdateien laden und diese später selbst auf GUI-Komponenten zeichnen möchte.

ImageIcon

Objekte von ImageIcon sind zur direkten Verwendung auf Swing-GUI-Komponenten wie bspw. JButtons, JLabels, JMenuItems etc. vorgesehen. Sie sind die Repräsentation von Icons oder Bildern, die auf der lokalen Festplatte, in einem Netzwerk, oder bereits im Speicher als Image-Objekt vorliegen.
javax.swing.ImageIcon ist also eine konkrete Implementierung des Icon-Interfaces, welches grundlegende Methoden für die Arbeit mit Icons oder Abbildungen definiert.
Das Laden der Bilder wird im Hintergrund bereits mit dem java.awt.Mediatracker überwacht, so dass die Bilder auf jeden Fall im Speicher sind, bevor sie gezeichnet werden.
Der Konstruktor von ImageIcon erwartet die Übergabe des Pfades zum Speicherort bzw. den Namen der zu ladenen Datei in Form eines Strings oder in Form eines Images oder einer URL. Demzufolge können Bilder aus Grafikdateien wie folgt in ImageIcon-Objekten referenziert werden:

Referenzierung eines Bildes mit Hilfe eines vorher geladenen Image-Objektes: <code=java> JButton button = null; ImageIcon icon = null;

try {

 Image image = ImageIO.read("demo.jpg");
 icon = new ImageIcon(image);

} catch(IOException e) {

 e.printStackTrace();

}

button = new JButton("Demo", icon);</code=java>

Das gleiche Ergebnis erhalten wir mit Referenzierung eines Bildes mit Hilfe des Dateinamens: <code=java> ImageIcon icon = new ImageIcon("demo.jpg"); JButton button = new JButton("Demo", icon);</code=java> Das Bild befindet sich bei diesem Beipsiel in der lokalen Verzeichnisebene, im gleichen Verzeichnis dieser Klasse.

Referenzierung eines Bildes in einem Netzwerk mit Hilfe einer URL: <code=java> ImageIcon icon = new ImageIcon(new URL("http://www.any-domain.de/images/demo.jpg")); JButton button = new JButton("Demo", icon);</code=java>

Referenzierung eines Bildes, welches in der gleichen Jar-Datei gespeichert wurde. <code=java> ImageIcon icon = new ImageIcon(getClass().getResource("demo.jpg")); JButton button = new JButton("Demo", icon);</code=java>


Ein ImageIcon wird immer nach den Vorgaben der einbettenden Komponente (JLabel, JButton etc.) gezeichnet bzw. angeordnet. Man hat hier also weniger Einfluss auf die Darstellung.

JApplets

In Swing-Applets kann für das effiziente Einlesen von Grafikdaten die Klasse javax.imageio.ImageIO verwendet werden.
Wie einfach ihre Benutzung ist, werden wir im Anschluss demonstrieren.

Einfache JApplet-Klassen

Zeichnen auf einen Container (JPanel), welcher dem JApplet als Hintergrund hinzugefügt wird: <code=java> import javax.swing.*; import javax.imageio.*; import java.awt.*; import java.net.*; import java.io.*;


public class PictureApplet extends JApplet {

  private Image image, background;
  public void init() {
     setContentPane(new MyContentPane());
     try {
        image = ImageIO.read(new URL(getCodeBase(), "icon.png"));
        background = ImageIO.read(new URL(getCodeBase(), "/images/background.jpg"));
     }
     catch(IllegalArgumentException iae) {

JOptionPane.showMessageDialog(this, "Grafikdatei nicht gefunden!");

     }
     catch(IOException ioe) {

JOptionPane.showMessageDialog(this, "Fehler beim Einlesen der Grafikdatei!");

     }
  }
  private class MyContentPane extends JPanel {
     protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if(background != null && image != null) { //beide Bilder müssen im Speicher liegen, sonst wird nichts gezeichnet
           g.drawImage(background, 0, 0, this);
           g.drawImage(image, 10, 10, this);
        }
     }
  }

}</code=java>

Link: Einbinden von Applets in HTML-Dateien

Applets in Jar-Dateien

Wenn sich das Applet gemeinsam mit der zu ladenden Grafik in einer Jar-Datei zusammengefasst wurden, muss die Grafik über den ClassLoader ins Applet geladen werden.

Zeichnen auf einen Container (JPanel), welcher dem JApplet als Hintergrund hinzugefügt wird: <code=java> import javax.swing.*; import javax.imageio.*; import java.awt.*; import java.net.*; import java.io.*;


public class PictureApplet extends JApplet {

  private Image image, background;
  public void init() {
     setContentPane(new MyContentPane());
     try {
        image = ImageIO.read(getClass().getResource("icon.png"));
        background = ImageIO.read(getClass().getResource("images/background.jpg"));
     }
     catch(IllegalArgumentException iae) {

JOptionPane.showMessageDialog(this, "Grafikdatei nicht gefunden!");

     }
     catch(IOException ioe) {

JOptionPane.showMessageDialog(this, "Fehler beim Einlesen der Grafikdatei!");

     }
  }
  private class MyContentPane extends JPanel {
     protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if(background != null && image != null) { //beide Bilder müssen im Speicher liegen, sonst wird nichts gezeichnet
           g.drawImage(background, 0, 0, this);
           g.drawImage(image, 10, 10, this);
        }
     }
  }

}</code=java>

Das Applet-Tag in der HTML-Datei muss jetzt angepasst werden, damit der Browser weiß, wo sich das Applet befindet.
Link: Applet-Tag für Jar-Dateien

Swing-Applikationen

Auch Swing-Applikationen werden im Allgemeinen in Jar-Archiven zusammengefasst. Diese lassen sich auf jedem System per Doppelklick starten, oder per Webstart über das Internet laden.
Hier also nun die Variante für eine Applikation. Auffällig ist, dass sich der Code des Panels gegenüber dem Applet nur leicht, der Code zum Laden der Bilder aber gar nicht verändert hat. <code=java> import javax.swing.*; import javax.imageio.*; import java.awt.*; import java.net.*; import java.io.*;

public class PicturePanel extends JPanel {

  private Image image, background;

  public PicturePanel() {
     super(new GridBagLayout());
     try {
        image = ImageIO.read(getClass().getResource("icons/icon.png"));
        background = ImageIO.read(getClass().getResource("images/background.jpg"));
     }
     catch(IllegalArgumentException iae) {
        JOptionPane.showMessageDialog(this, "Grafikdatei nicht gefunden!\n"+iae.getMessage());
     }
     catch(IOException ioe) {
        JOptionPane.showMessageDialog(this, "Fehler beim Einlesen einer Grafikdatei!\n"+ioe.getMessage());
     }
     JButton pictureButton = new JButton("Button mit Icon", new ImageIcon(image));
     add(pictureButton);
  }

  @Override
  protected void paintComponent(Graphics g) {
     super.paintComponent(g);
     if(background != null) { //Bild muss im Speicher liegen, sonst wird nichts gezeichnet
        g.drawImage(background, 0, 0, this);
     }
  }

} </code=java>

Das Ganze kann nun in einem Fenster angezeigt werden: <code=java> import javax.swing.*;

public class PictureFrame {

 public static void main(String[] args) {
   SwingUtilities.invokeLater(new Runnable() {
     public void run() {
       JFrame f = new JFrame("Bildbetrachter");
       f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       f.add(new PicturePanel());
       f.pack();
       f.setLocationRelativeTo(null);
       f.setVisible(true);
     }
   });
 }

} </code=java>

Grafiken aus externen Jar-Dateien laden

Im Internet sind reichlich freie Quellen zu finden, die spezielle Grafiken (Icons) für die visuelle Kennzeichnung und Verschönerung von GUI-Elementen eingesetzt werden können.
Diese Quellen liegen meist in gepackter Form als Zip-Archiv vor. Um sie für ein Java-Programm nutzbar zu machen, kann man eine solche Zip-Datei einfach in eine Jar-Datei umbenennen (Datei-Präfix ändern) oder auch, unter Beachtung der Lizenz, einzelne Dateien extrahieren und in eine neue Jar-Datei packen. Den Weg, Grafikdateien aus der Jar-Datei zu laden, die auch unser Programm enthält, haben wir bereits oben gesehen. Wir werden uns nun aber mal ansehen, wie Grafikdateien aus externen Bibliotheken bzw. Archiven geladen werden können.

Bibliotheken im Classpath

Bibliotheken außerhalb des Classpaths

Um Grafikdateien aus einer externen Jar-Datei zu laden, die sich nicht im Sichtbarkeitsbereich des ClassLoaders befindet, kann man den absoluten Pfad zur Jar-Datei entsprechend zusammenbauen: <code=java>URL jarPath = new URL("jar:file:Path/To/JarFile/jarfile.jar!"+innerPath);</code=java> innerPath bezeichnet hierbei den Pfad innerhalb der externen Jar-Datei zur Grafikdatei.