Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Sprachbeschreibung
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Eigene Klassen schreiben
6 Exceptions
7 Generics<T>
8 Äußere.innere Klassen
9 Besondere Klassen der Java SE
10 Architektur, Design und angewandte Objektorientierung
11 Die Klassenbibliothek
12 Bits und Bytes und Mathematisches
13 Datenstrukturen und Algorithmen
14 Threads und nebenläufige Programmierung
15 Raum und Zeit
16 Dateien, Verzeichnisse und Dateizugriffe
17 Datenströme
18 Die eXtensible Markup Language (XML)
19 Grafische Oberflächen mit Swing
20 Grafikprogrammierung
21 Netzwerkprogrammierung
22 Verteilte Programmierung mit RMI
23 JavaServer Pages und Servlets
24 Datenbankmanagement mit JDBC
25 Reflection und Annotationen
26 Dienstprogramme für die Java-Umgebung
A Die Begleit-DVD
Stichwort
Ihre Meinung?

Spacer
 <<   zurück
Java ist auch eine Insel von Christian Ullenboom
Das umfassende Handbuch
Buch: Java ist auch eine Insel

Java ist auch eine Insel
geb., mit DVD
1482 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1506-0
Pfeil 20 Grafikprogrammierung
  Pfeil 20.1 Grundlegendes zum Zeichnen
    Pfeil 20.1.1 Die paint()-Methode für das AWT-Frame
    Pfeil 20.1.2 Zeichnen von Inhalten auf ein JFrame
    Pfeil 20.1.3 Auffordern zum Neuzeichnen mit »repaint()«
    Pfeil 20.1.4 Java 2D-API
  Pfeil 20.2 Einfache Zeichenmethoden
    Pfeil 20.2.1 Linien
    Pfeil 20.2.2 Rechtecke
    Pfeil 20.2.3 Ovale und Kreisbögen
    Pfeil 20.2.4 Polygone und Polylines
  Pfeil 20.3 Zeichenketten schreiben und Fonts
    Pfeil 20.3.1 Zeichenfolgen schreiben
    Pfeil 20.3.2 Die Font-Klasse
    Pfeil 20.3.3 Einen neuen Font aus einem gegebenen Font ableiten
    Pfeil 20.3.4 Zeichensätze des Systems ermitteln *
    Pfeil 20.3.5 Neue TrueType-Fonts in Java nutzen
    Pfeil 20.3.6 Font-Metadaten durch FontMetrics *
  Pfeil 20.4 Geometrische Objekte
    Pfeil 20.4.1 Die Schnittstelle Shape
    Pfeil 20.4.2 Kreisförmiges
    Pfeil 20.4.3 Kurviges *
    Pfeil 20.4.4 Area und die konstruktive Flächengeometrie *
    Pfeil 20.4.5 Pfade *
    Pfeil 20.4.6 Punkt in einer Form, Schnitt von Linien, Abstand Punkt/Linie *
  Pfeil 20.5 Das Innere und Äußere einer Form
    Pfeil 20.5.1 Farben und die Paint-Schnittstelle
    Pfeil 20.5.2 Farben mit der Klasse »Color«
    Pfeil 20.5.3 Die Farben des Systems über SystemColor *
    Pfeil 20.5.4 Composite und Xor *
    Pfeil 20.5.5 Dicke und Art der Linien von Formen bestimmen über »Stroke« *
  Pfeil 20.6 Bilder
    Pfeil 20.6.1 Eine Übersicht über die Bilder-Bibliotheken
    Pfeil 20.6.2 Bilder mit »ImageIO« lesen
    Pfeil 20.6.3 Ein Bild zeichnen
    Pfeil 20.6.4 Programm-Icon/Fenster-Icon setzen
    Pfeil 20.6.5 Splash-Screen *
    Pfeil 20.6.6 Bilder im Speicher erzeugen *
    Pfeil 20.6.7 Pixel für Pixel auslesen und schreiben *
    Pfeil 20.6.8 Bilder skalieren *
    Pfeil 20.6.9 Schreiben mit ImageIO
    Pfeil 20.6.10 Asynchrones Laden mit getImage() und dem MediaTracker *
  Pfeil 20.7 Zum Weiterlesen


Rheinwerk Computing - Zum Seitenanfang

20.6 Bilder  Zur nächsten ÜberschriftZur vorigen Überschrift

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 und ihre Form ändern. Über Java können GIF-, PNG- und JPEG-Bilder geladen werden.


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-Packverfahren [Benannt nach den Erfindern Lempel, Ziv und Welch.] 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 Komprimierverfahren speichert die Bilder mit einer 24-Bit-Farbpalette. Der Komprimierungsfaktor kann prozentual eingestellt werden.


Jede Grafik wird als Exemplar der Klasse Image erzeugt, wobei einige Lademethoden auch Exemplare der Unterklasse BufferedImage liefern.


Rheinwerk Computing - Zum Seitenanfang

20.6.1 Eine Übersicht über die Bilder-Bibliotheken  Zur nächsten ÜberschriftZur vorigen Überschrift

Die Java-API bietet – historisch gewachsen – mehrere Möglichkeiten zum Laden und für einige Formate auch zum Speichern von Bildern an. Zudem gibt es Zusatzbibliotheken für Spezialformate und besondere Anforderungen, wie etwa die Verwaltung sehr großer Grafiken:

  • Die Methode getImage() der Klassen Toolkit (bei Applikationen) und Applet (bei Applets) liefert ein Image-Objekt.
  • Der Media-Tracker lädt Bilder und informiert über den Ladevorgang.
  • Die Klasse ImageIcon lädt für Swing Bilder, die sich direkt auf der grafischen Oberfläche auf Komponenten wie Schaltflächen platzieren lassen. Sie nutzt im Hintergrund den Media-Tracker.
  • Seit Java 1.4 gibt es das Paket javax.imageio, um das Lesen und Schreiben von Grafiken zu vereinheitlichen. Die Klasse ImageIO bietet eine einfache statische Methode read().
  • Das Paket com.sun.image.codec.jpeg beherbergt seit Java 1.2 Typen zum Lesen und Schreiben von JPEGs. Der Paketname zeigt an, dass es nicht ganz offiziell ist und damit nicht jeder Java-Implementierung bekannt sein muss.
  • Über die externe Java-Bibliothek JAI (Java Advanced Imaging API) kommen Formate wie TIFF und WBMP dazu. Informationen gibt die Seite http://java.sun.com/products/java-media/jai/iio.html.
  • JIMI (Java Image Management Interface) ist eine hundertprozentige Java-Klassenbibliothek, die hauptsächlich Lade- und Speicherroutinen für Bilder zur Verfügung stellt. Die Klasse JimiUtils stellt beispielsweise eine statische getThumbnail()-Methode bereit, die zu einer Datei ein Vorschaubild als Image-Objekt berechnet. Ebenso stellt JIMI Möglichkeiten zur Anzeige bereit, um etwa sehr große Grafiken speichersparend zu verwalten. Diese Technik nennt sich Smart-Scrolling und kann von der JimiCanvas-Komponente übernommen werden. So wird nur jener Bildteil im Speicher gehalten, der gerade sichtbar ist. Für die Speicherverwaltung bietet JIMI ein eigenes Speicherverwaltungssystem, das VMM (Virtual Memory Management), ebenso wie eine eigene Image-Klasse, die schnelleren Zugriff auf die Pixelwerte erlaubt. Zusätzlich bietet JIMI eine Reihe von Filtern für Rotation und Helligkeitsanpassung, die auf JIMI- und AWT-Bildern arbeiten. Auch Farbreduktion ist ein Teil von JIMI. JIMI-Bilder lassen sich im Gegensatz zu den bekannten AWT-Bildern serialisieren.

Für exotische Formate – etwa das Windows Icon-Format – hilft nur eine Suche im Web. Im Fall der ICO-Dateien hilft die freie Bibliothek AC.lib ICO unter http://www.acproductions.de/commercial/aclibico/.


Rheinwerk Computing - Zum Seitenanfang

20.6.2 Bilder mit »ImageIO« lesen  Zur nächsten ÜberschriftZur vorigen Überschrift

ImageIO ist sehr einfach zu nutzen, denn mit einer kleinen statischen Methode ImageIO .read() ist die Grafik geladen. Unterstützte Dateiformate sind sicher GIF, JPEG und PNG; weitere Formate können von Plattform zu Plattform unterschiedlich sein (eine präzisere Liste der angemeldeten Leser liefert ImageIO.getReaderFormatNames() und ImageIO.getReaderMIMETypes()).


final class javax.imageio.ImageIO

  • static BufferedImage read( File input ) throws IOException
  • static BufferedImage read( InputStream input ) throws IOException
  • static BufferedImage read( URL input ) throws IOException
  • static BufferedImage read( ImageInputStream input ) throws IOException Lädt ein Bild und liefert ein BufferedImage oder null, wenn kein Decoder das Bild lesen konnte.

Beispiel Lesen eines Bildes aus einer Datei, Netzwerkdatenquelle und URL:

BufferedImage a = ImageIO.read( new File( "girlfriend1001.png" ) );
BufferedImage b = ImageIO.read( socket.getInputStream() );
BufferedImage c = ImageIO.read( new URL("http://www.tutego.com/images/Umbruch
email.gif") );

Die Bilder können auf unterschiedliche Art weiterverarbeitet werden. Sie lassen sich über drawImage() anzeigen und auch als Grafiken in Swing weiterverarbeiten. Zwar fordert Swing sie als ImageIcon an, doch die Klasse ist so gütig, einen Konstruktor anzubieten, der ein Image-Objekt akzeptiert.

Bilder in Applets und alten Java-Versionen *

Ab der Version Java 1.4 steht die Klasse ImageIO zur Verfügung. Für Programme vor Java 1.4 muss die getImage()-Methode vom Toolkit oder im Fall von Applets die Methode getImage() von Applet verwendet werden.


class java.applet.Applet
extends Panel

  • Image getImage( URL url ) Lädt ein durch die URL angegebenes Bild.

Müssen wir in einem Applet die Grafik relativ zu einem Bezugspunkt angeben, der jedoch fehlt, so hilft uns die Methode getCodeBase() weiter, die uns die relative Adresse des Applets übergibt (mit getDocumentBase() bekommen wir die URL des HTML-Dokuments, unter der das Applet eingebunden ist).

Genau genommen lädt getImage() das Bild nicht sofort, anders als read() von ImageIO. Ein Image-Objekt wird gültig erzeugt und das Objekt mit der Grafik in Verbindung gebracht, aber es wird erst dann aus der Datei beziehungsweise dem Netz geladen, wenn der erste Zeichenaufruf stattfindet. Somit schützt uns die Bibliothek vor unvorhersehbaren Ladevorgängen für Bilder, die später oder gar nicht genutzt werden.


Rheinwerk Computing - Zum Seitenanfang

20.6.3 Ein Bild zeichnen  Zur nächsten ÜberschriftZur vorigen Überschrift

Eine Grafik zeichnet die Methode drawImage() der Graphics-Klasse. Die Methode ist mit unterschiedlichen Varianten überladen, um die Grafik auch in anderen Größen zu zeichnen – was sie skaliert – oder auch nur Teile zu zeichnen. Der einfachste Aufruf, der die Grafik in ihrer Originalgröße ab der Position oben links mit der Position (0,0) setzt, ist:

Image image = ...
g.drawImage( image, 0, 0, this );

Die drawImage()-Methoden sind mehrheitlich in der Oberklasse Graphics, doch zwei zusätzliche Methoden deklariert die Graphics-Unterkasse Graphics2D. Auf die Modifizierer abstract und die Rückgabe boolean verzichtet die erste Aufzählung der Kürze halber.


abstract class java.awt.Graphics

  • drawImage( Image img, int x, int y, ImageObserver observer )
  • drawImage( Image img, int x, int y, Color bgcolor, ImageObserver observer )
  • drawImage( Image img, int x, int y, int width, int height, ImageObserver observer )
  • drawImage( Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer )
  • drawImage( Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer )
  • drawImage( Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer )

abstract class java.awt.Graphics2D
extends Graphics

  • boolean drawImage( Image img, AffineTransform xform, ImageObserver obs )
  • void drawImage( BufferedImage img, BufferedImageOp op, int x, int y )

Hinweis In den Methoden fällt ein besonderer Beobachter, der ImageObserver, auf. Der Grund für seinen Einsatz ist die Tatsache, dass Java bei den über das Toolkit angesprochenen Grafiken das Laden so weit hinauszögert, bis eine Darstellung die Pixel-Daten wirklich erforderlich macht. Damit aber nach (oder während) des Ladens die Darstellung erfolgen kann, informiert der Lader die Interessenten über den Ladezustand. Nutzen wir drawImage() in einer Unterklasse von Component – sie implementiert ImageObserver –, ist das Argument für den ImageObserver oft this, andernfalls null, wenn eine Ladeüberwachung nicht nötig ist.


Bildbetrachter

Das folgende Programmlisting zeigt eine einfache Applikation mit einer Menüleiste, die über einen Dateiauswahldialog eine Grafik lädt und anzeigt. Wir beginnen mit der ersten Klasse, die eine Swing-Komponente darstellt, die das Bild zeichnet:

Listing 20.16  com/tutego/insel/ui/image/ImageViewer.java, ImageComponent

class ImageComponent extends JComponent
{
  private static final long serialVersionUID = 8055865896136562197L;

  private BufferedImage image;

  public void setImage( BufferedImage image )
  {
    this.image = image;
    setPreferredSize( new Dimension(image.getWidth(), image.getHeight()) );
    repaint();
    invalidate();
  }

  @Override
  protected void paintComponent( Graphics g )
  {
    if ( image != null )
      g.drawImage( image, 0, 0, this );
  }
}

Da ein Dateiauswahl-Dialog gewünscht ist, der aufgrund einer Menüauswahl die Datei lädt, folgt eine Implementierung einer Swing-Aktion:

Listing 20.17  com/tutego/insel/ui/image/ImageViewer.java, FileOpenAction

class FileOpenAction extends AbstractAction
{
  private final ImageComponent viewComponent;

  public FileOpenAction( ImageComponent viewComponent )
  {
    this.viewComponent = viewComponent;

    putValue( NAME,            "Öffnen" );
    putValue( ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_O, Umbruch
                                          InputEvent.CTRL_DOWN_MASK ) );
    putValue( MNEMONIC_KEY,    (int) 'f' );
  }

  public void actionPerformed( ActionEvent e )
  {
    JFileChooser fileDialog = new JFileChooser();
    fileDialog.setFileFilter( new FileNameExtensionFilter("*.jpg;*.gif", Umbruch
      "jpg", "gif") );
    fileDialog.showOpenDialog( viewComponent );
    final File file = fileDialog.getSelectedFile();

    if ( file != null )
    {
      new SwingWorker<BufferedImage, Void>() {
        @Override protected BufferedImage doInBackground() throws IOException {
          return ImageIO.read( file );
        }
        @Override protected void done() {
          try { viewComponent.setImage( get() ); } catch ( Exception e ) { }
        }
      }.execute();
    }
  }
}

Der Dialog zur Dateiauswahl ist so über einen FileFilter eingestellt, dass er nur Verzeichnisse und Dateien anzeigt, die auf ».jpg« oder ».gif« enden. Hat der Benutzer eine gültige Grafik ausgewählt, wird setImage() unserer ImageComponent angewiesen, das Bild zu laden und anzuzeigen.

Den letzten Teil bildet das Hauptprogramm. Es erzeugt die Bild-Komponente und das Menü, setzt den Listener und zeigt das Fenster an:

Listing 20.18  com/tutego/insel/ui/image/ImageViewer.java, ImageViewer

public class ImageViewer
{
  public static void main( String[] args )
  {
    JFrame f = new JFrame( "Bildbetrachter" );

    ImageComponent imageComponent = new ImageComponent();
    f.add( new JScrollPane(imageComponent) );
    JMenuBar menuBar = new JMenuBar();
    JMenu menu = new JMenu( "Datei" );
    menu.setMnemonic( 'D' );
    menu.add( new JMenuItem( new FileOpenAction(imageComponent) ) );
    menuBar.add( menu );
    f.setJMenuBar( menuBar );

    f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    f.setSize( 600, 400 );
    f.setVisible( true );
  }
}

Abbildung 20.13  Ein einfacher Bildbetrachter mit Dateiauswahldialog


Rheinwerk Computing - Zum Seitenanfang

20.6.4 Programm-Icon/Fenster-Icon setzen  Zur nächsten ÜberschriftZur vorigen Überschrift

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 Java durch die setIconImage()-Methode setzen. Der Methode wird ein Image-Objekt übergeben, das die Grafik der Größe 16 × 16 Pixel beinhaltet; andere Größen werden skaliert, was nicht immer so toll aussieht:

Listing 20.19  com/tutego/insel/ui/image/FramesIconImage, main()

JFrame f = new JFrame();
try
{
  Image img = ImageIO.read( FramesIconImage.class.getResource( "discovery.gif" ) );
  f.setIconImage( img );
}
catch ( IOException e ) { e.printStackTrace(); }
f.setVisible( true );

Mit getResource() bezieht read() die Datei aus dem Klassenpfad und kann so auch in einem Java-Archiv eingebettet sein.


class java.awt.Frame
extends Window
implements MenuContainer

  • void setIconImage( Image image ) Ordnet dem Fenster eine kleine Grafik zu. Nicht alle grafischen Oberflächen erlauben diese Zuordnung.

Rheinwerk Computing - Zum Seitenanfang

20.6.5 Splash-Screen *  Zur nächsten ÜberschriftZur vorigen Überschrift

Ein Splash-Screen ist ein Willkommens-Bildschirm mit Grafik, der noch vor dem eigentlichen Programmstart über die JVM erscheint und dem Benutzer Informationen über Version und Autor übermittelt. Java kann in zwei Varianten einen Startschirm mit einer beliebigen Grafik – nennen wir sie beispielsweise splash.png – anzeigen:

  • über den Schalter -splash beim Start der JVM; etwa java -splash:splash.png Main
  • mit einem Eintrag Splashscreen-Image: splash.png in der Manifest-Datei

Öffnet unser Java-Programm das erste Fenster, schließt sich der Splash-Screen automatisch. Dennoch gibt es Möglichkeiten, auf den Splash-Screen aus dem Java-Programm zuzugreifen:

SplashScreen splash = SplashScreen.getSplashScreen();
Graphics2D g2 = splash.createGraphics();
// Zeichenoperationen
splash.update();

Die Methode close() schließt manuell den Splash-Screen und wartet nicht auf das erste eigene Fenster, was den Splash-Screen automatisch schließt.


Rheinwerk Computing - Zum Seitenanfang

20.6.6 Bilder im Speicher erzeugen *  Zur nächsten ÜberschriftZur vorigen Überschrift

Nicht immer kommen die Bilder vom Datensystem oder aus dem Internet. Mit der Java-Bibliothek lassen sich einfach auch eigene (Buffered)Image-Objekte anlegen. Dazu bieten sich – wieder historisch bedingt – verschiedene Varianten an:

  • Jede AWT-Komponente, wie Frame oder Panel, bietet die Methode createImage(). Die Anweisung Image image = panel.createImage(800, 600); erzeugt ein Image-Objekt mit 800 Pixeln in der Breite und 600 in der Höhe, das mit getGraphics() Zugriff auf den Grafikkontext bietet. Wenn die AWT-Komponente noch nicht angezeigt wurde, liefert createImage() die Rückgabe null, sodass hier leicht eine NullPointerException entstehen kann. Auch unterstützen die Bilder keine Transparenz.
  • Aus den Einschränkungen heraus führte Java 1.2 die Klasse BufferedImage ein, die eine Erweiterung der Image-Klasse ist. Beim Erzeugen ist immer ein Bildtyp anzugeben, der über die physikalische Speicherung bestimmt.
  • createCompatibleImage() über GraphicsConfiguration erzeugt ein BufferedImage und benötigt keinen Bildtyp.

BufferedImage erzeugen lassen

Ein Bild über createCompatibleImage() zu erzeugen, hat den großen Vorteil, dass das Daten- und Farbmodell optimal gewählt ist. Der einzige Nachteil dieser Methode ist die große Menge an benötigten Hilfsobjekten – was zusätzliche Schreibarbeit bedeutet:

Listing 20.20  com/tutego/insel/ui/image/CreateCompatibleImageDemo.java, main()

GraphicsConfiguration gfxConf = GraphicsEnvironment.getLocalGraphicsEnvironment().
    getDefaultScreenDevice().getDefaultConfiguration();
int width = 600, height = 400;
BufferedImage image = gfxConf.createCompatibleImage( width, height );

Von createCompatibleImage() gibt es auch eine Variante, die die Angabe einer Transparenz ermöglicht.


abstract class java.awt.GraphicsConfiguration

  • abstract BufferedImage createCompatibleImage( int width, int height ) Erzeugt ein BufferedImage.
  • BufferedImage createCompatibleImage( int width, int height, int transparency ) Erzeugt ein BufferedImage mit optionaler Transparenz. Das Argument für transparency kann sein: Transparency.OPAQUE (keine Transparenz, der Alpha-Wert ist 1,0), Transparency.BITMASK (Bilddaten sind komplett sichtbar, also opak mit Alpha-Wert 1, oder transparent, also Alpha-Wert 0), Transparency.TRANSLUCENT (Grafik erlaubt das Durchscheinen mit Alpha-Werten von 0,0 bis 1,0).

Das Bild bemalen

Image-Objekte (BufferedImage ist eine Unterklasse) geben über getGraphics() das Graphics-Objekt zurück, mit dem sich das Bild bemalen lässt. Im Fall eines speziellen BufferedImage-Objekts ist es jedoch üblich, die Methode createGraphics() einzusetzen, da sie ein Graphics2D-Objekt – eine Unterklasse von Graphics – liefert, mit dem weitere Zeichenoperationen möglich sind. Außerdem ruft getGraphics() sowieso createGraphics() auf …

Graphics2D g = img.createGraphics();
g.setColor( Color.WHITE );
g.fillRect( 0, 0, b – 1, h – 1 );

Alternativ kann zum Löschen des Hintergrunds auch g.setBackground(Color.WHITE); g.clearRect(Argumente); verwendet werden.

BufferedImage von Hand erzeugen

Der Konstruktor der Klasse BufferedImage wird mit den Maßen parametrisiert und zusätzlich mit einem Speichermodell für die Bildinformationen. Das ermöglicht die Verwendung von beliebigen Farb- und Speichermodellen:

int h = 400,
    b = 600;
BufferedImage img = new BufferedImage( b, h, BufferedImage.TYPE_INT_RGB );

Das notwendige dritte Argument kennzeichnet den Speichertyp; hier sind die Farben durch je 8 Bit Rot, Grün und Blau abgebildet. Um weitere zwei der über 10 Bildtypen zu nennen: TYPE_USHORT_GRAY (Graubilder) oder TYPE_INT_ARGB (RGB mit jeweils 8 Bit sowie Alpha).


class java.awt.image.BufferedImage
extends Image
implements RenderedImage, Transparency, WritableRenderedImage

  • BufferedImage( int width, int height, int imageType ) Liefert ein neues Hintergrundbild mit den gegebenen Maßen.

Rheinwerk Computing - Zum Seitenanfang

20.6.7 Pixel für Pixel auslesen und schreiben *  Zur nächsten ÜberschriftZur vorigen Überschrift

Die Klasse BufferedImage – aber nicht die Basisklasse Image – ermöglicht mit getRGB() das Auslesen einzelner Farbwerte und mit setRGB() das Setzen.


Beispiel Lies die Farbwerte eines BufferedImage-Objekts image, und zerlege die Rückgabe in die Farbwerte Rot, Grün, Blau und den Alpha-Wert:

int argb  = image.getRGB( x, y );
int alpha = (argb >> 24) & 0xff;
int red   = (argb >> 16) & 0xff;
int green = (argb >> 8)  & 0xff;
int blue  = (argb)       & 0xff;

Die Methode getRGB() liefert als Rückgabe einen Wert im Standard-RGB-Modell BufferedImage.TYPE_INT_ARGB – unabhängig von der tatsächlichen physikalischen Kodierung – und im Standard-RGB-Farbraum. Die Farbwerte sind daher an ihren wohldefinierten Plätzen. Eine Hilfsmethode zum Extrahieren bietet die Color-Klasse, doch muss für diese Zwecke zuerst ein Objekt aufgebaut werden, was nicht so optimal ist. Das folgende Listing zeigt ein Beispiel.

Eine zweite überladene Methode getRGB() kopiert aus einem Bildausschnitt alle Pixel in ein Feld.


Beispiel Kopiere alle Farbwerte eines BufferedImage in ein Feld:

int w = image.getWidth(), h = image.getHeight();
int[] argbArray = new int[ w * h ];
image.getRGB( 0 /* startX */, 0 /* startY */,
              w,  h, argbArray,
              0 /* offset */, w /* scansize */ );

Der Offset bestimmt die Verschiebung im Feld und scansize die Zeilenbreite. Damit liefert argbArray[offset + (y-startY)*scansize + (x-startX)] das Pixel im Feld.


Wünschen wir lediglich ein Teilbild als BufferedImage, führt uns getSubimage(int x, int y, int w, int h) zum Ziel.

Zum Überschreiben der Pixel bietet die Klasse BufferedImage symmetrische setRGB()-Methoden.


Beispiel Gib dem Pixel an der Stelle x, y die Farbe von Color.LIGHT_GRAY:

int argb = Color.LIGHT_GRAY.getRGB();
image.setRGB( x, y, argb );

Ein Beispiel zum Lesen und Schreiben von Pixeln

Das folgende Programm lädt über ImageIO ein Bild und gibt die Farbinformationen – also die Anteile Rot, Grün, Blau – beim Bewegen der Maus über das Bild auf der Konsole aus. Die Ereignisbehandlung übernimmt ein MouseMotionListener. Nach der Ausgabe bitten wir die Color-Klasse um einen dunkleren Farbton, und setRGB() überschreibt den vorherigen Farbwert für das Pixel:

Listing 20.21  com/tutego/insel/ui/image/ImageGrabber.java, Ausschnitt

@Override public void mouseMoved( MouseEvent e )
{
  int pixel = image.getRGB( e.getX(), e.getY() );

  int red   = (pixel >> 16) & 0xFF,
      green = (pixel >> 8) & 0xFF,
      blue  = (pixel) & 0xFF;

  System.out.println( "R=" + red + " G=" + green + " B=" + blue );

  image.setRGB( e.getX(), e.getY(), new Color(pixel).darker().getRGB() );
  repaint();
}

class java.awt.image.BufferedImage
extends Image
implements WritableRenderedImage, Transparency

  • int getRGB( int x, int y ) Liefert den Farbwert vom Punkt x, y im Format TYPE_INT_ARGB.
  • int[] getRGB( int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize ) Liefert die Farbinformationen eines Bildausschnitts.
  • void setRGB( int x, int y, int rgb ) Setzt den Farbwert an der Stelle x, y auf rgb.
  • void setRGB( int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize ) Setzt die Farbwerte mehrerer Pixel.

Rheinwerk Computing - Zum Seitenanfang

20.6.8 Bilder skalieren *  Zur nächsten ÜberschriftZur vorigen Überschrift

Die Methode getScaledInstance() der Klasse Image gibt ein neues Image-Objekt mit größeren oder kleineren Maßen zurück. Das neue Bild wird wieder nur dann berechnet, wenn es auch benötigt wird – das Verhalten ist also ebenso asynchron wie bei der gesamten Bildverwaltung über die Image-Klasse. Beim Vergrößern oder Verkleinern kommt es zu Pixelfehlern, und das Vergrößern der Pixel beeinflusst das Endergebnis und die Geschwindigkeit. Stellen wir uns vor, ein Bild der Größe 100 × 100 Pixel soll um das Doppelte vergrößert werden. Das Resultat ist ein Bild mit 200 × 200 Pixeln, doch muss aus einem Bildpunkt nun die Information für drei weitere Punkte abgeleitet werden. Eine Lösung bestünde darin, die Farbwerte der Punkte einfach zu duplizieren, dann bleibt die Schärfe, aber das Bild wirkt wie aus groben Blöcken zusammengesetzt. Eine andere Möglichkeit wäre, die Farbinformationen für die neuen Punkte aus den Informationen der Nachbarpunkte zu errechnen. Das Bild wirkt glatter, aber auch unschärfer bei hoher Skalierung. Und ebenso wie beim Vergrößern der Bilder sollten auch beim Verkleinern die Bildinformationen nicht einfach wegfallen, sondern, wenn möglich, zu neuen Farbwerten zusammengefasst werden. So erwarten wir von einem Algorithmus, dass dieser bei einer Schrumpfung von drei Farbwerten zu einem Farbwert die drei Informationen zu einem neuen Wert zusammenlegt.

Damit diese Anforderungen erfüllt werden können, verlangt getScaledInstance() nicht nur die neue Breite und Höhe, sondern auch eine Konstante für die Art der Skalierung. Der Parameter bestimmt den Algorithmus – mögliche Konstanten sind SCALE_DEFAULT, SCALE_FAST, SCALE_SMOOTH, SCALE_REPLICATE und SCALE_AREA_AVERAGING.


Tabelle 20.3  Argumente für »getScaledImage()«

Skalierungsparameter Bedeutung

SCALE_DEFAULT

Verwendet einen Standard-Skalierungsalgorithmus.

SCALE_FAST

Verwendet einen Skalierungsalgorithmus, der mehr Wert auf Geschwindigkeit als auf die Glätte des Bilds legt.

SCALE_SMOOTH

Verwendet einen Algorithmus mit guter Bildqualität und legt weniger Wert auf Geschwindigkeit.

SCALE_REPLICATE

Benutzt für den Skalierungsalgorithmus den ReplicateScaleFilter.

SCALE_AREA_AVERAGING

Verwendet den AreaAveragingScaleFilter.


Mit Hilfe dieser Konstanten lässt sich die Methode aufrufen:


abstract class java.awt.Image

  • Image getScaledInstance( int width, int height, int hints ) Liefert ein skaliertes Bild mit den neuen Maßen width und height. Das neue Bild kann asynchron gefördert werden. hints gibt den Skalierungsalgorithmus als Konstante an. Ist die Höhe oder Breite negativ, so berechnet sich der Wert aus dem anderen, um das Seitenverhältnis beizubehalten.

Beispiel Eine Grafik soll geladen, und zwei skalierte neue Image-Exemplare sollen abgeleitet werden. Die erste Skalierung soll das Original um einen Prozentwert verändern, und die zweite Skalierung soll – unabhängig von der korrekten Wiedergabe der Seitenverhältnisse – das Bild auf die Größe des Bildschirms bringen. Wir wollen es mit Image.SCALE_SMOOTH skaliert haben:

Image image = ImageIO.read( "ottosHaus.jpg" );
int   percent = 175;
Image scaled1 = image.getScaledInstance(
  (image.getWidth() * percent) / 100,
  (image.getHeight() * percent) / 100,
  Image.SCALE_SMOOTH );
Image scaled2 = image.getScaledInstance(
  Toolkit.getDefaultToolkit().getScreenSize().width,
  Toolkit.getDefaultToolkit().getScreenSize().height,
  Image.SCALE_SMOOTH );

Hinter den Kulissen

Was auf den ersten Blick wie die Wahl zwischen unglaublich vielen Varianten aussieht, entpuppt sich als typische Informatiker-Lösung: entweder schnell und schmutzig oder schön und gemächlich. Aber so ist nun mal das Leben. Der Quelltext macht dies deutlich:

public Image getScaledInstance(int width, int height, int hints)
{
  ImageFilter filter;
  if ((hints & (SCALE_SMOOTH | SCALE_AREA_AVERAGING)) != 0)
    filter = new AreaAveragingScaleFilter(width, height);
  else
    filter = new ReplicateScaleFilter(width, height);
  ImageProducer prod;
  prod = new FilteredImageSource(getSource(), filter);
  return Toolkit.getDefaultToolkit().createImage(prod);
}

Bei der Wahl zwischen sanftem Bild und schnellem Algorithmus greift getScaledInstance() auf die beiden Filterklassen AreaAveragingScaleFilter und ReplicateScaleFilter zurück. Sie berechnen jeweils das neue Bild über einen Bildproduzenten. ReplicateScaleFilter ist der einfachere von beiden. Bei der Vergrößerung werden die Pixel einer Zeile oder Spalte einfach verdoppelt. Wird verkleinert, so werden einfach Reihen oder Spalten weggelassen. Mit einem AreaAveragingScaleFilter bekommen wir die besseren Resultate, da Pixel nicht einfach kopiert werden, sondern weil wir eingefügte Pixel aus einer Mittelwertberechnung erhalten. Der Algorithmus heißt im Englischen auch nearest neighbor algorithm.


Rheinwerk Computing - Zum Seitenanfang

20.6.9 Schreiben mit ImageIO  Zur nächsten ÜberschriftZur vorigen Überschrift

ImageIO ist eine Utility-Klasse mit statischen Methoden zum Lesen und Schreiben von Grafiken und zum Raussuchen eines passenden Bildlesers/-schreibers.

So wie die statische Methode ImageIO.read() eine Grafik liest, schreibt ImageIO.write() sie zum Beispiel im PNG- oder JPG-Format. Voraussetzung ist eine Grafik, die als RenderedImage vorliegt. Die Schnittstelle wird beispielsweise von BufferedImage implementiert, der wichtigsten Klasse für Bildinformationen. Gilt es, die Grafik abzuspeichern, wird die Methode ImageIO.write() mit einem Verweis auf das RenderedImage sowie das Datenformat und ein File-Objekt aufgerufen.


Beispiel Speichere als PNG-Datei ein Bild mit den Maßen 100 × 100 und einem gefüllten Kreis:

Listing 20.22  com/tutego/insel/ui/image/SaveImage.java, main()

GraphicsConfiguration gfxConf = GraphicsEnvironment
    .getLocalGraphicsEnvironment().getDefaultScreenDevice()
    .getDefaultConfiguration();
BufferedImage image = gfxConf.createCompatibleImage( 100, 100 );
image.createGraphics().fillOval( 0, 0, 100, 100 );
ImageIO.write( image, "png", new File( "c:/circle.png" ) );

Kann »ImageIO« ein Format behandeln? *

ImageIO erlaubt standardmäßig das Speichern in JPG und PNG und – seit Java 6 – GIF. [Für ältere Java-Versionen bieten sich die Klassen GIFEncoder von Adam Doppelt an (http://www.gurge.com/amd/old/java/GIFEncoder/index.html) oder GifEncoder von Jef Poskanzer (http://www.acme.com/java/software/Acme.JPM.Encoders.GifEncoder.html). ] Eine Liste der unterstützten Formate liefert ImageIO.getWriterFormatNames() beziehungsweise Image-IO.getWriterMIMETypes():

String[] types = ImageIO.getWriterMIMETypes();
System.out.println( Arrays.toString(types) );

Die Ausgabe ist unter der kommenden Version Java 7:

[image/jpeg, image/png, image/x-png, image/vnd.wap.wbmp, image/gif, image/bmp]

Ob ImageIO ein Bild mit einem bestimmten Grafikformat lesen kann, bestimmt im Grunde die statische Methode ImageIO.getImageReadersByFormatName() – sie liefert eine Liste von ImageReader-Objekten, die das Format übernähmen. Da die Liste über einen Iterator gegeben ist, lässt sich die Frage, ob ImageIO ein bestimmtes Format lesen kann, über das Ergebnis von ImageIO.getImageReadersByFormatName().hasNext() beantworten.


Beispiel Soll für eine Endung die Möglichkeit des Lesens erfragt werden, liefert eine eigene Methode canReadExtension() die Antwort – wieder über einen Iterator:

public static boolean canReadExtension( String ext )
{
  return ImageIO.getImageReadersBySuffix(ext).iter.hasNext();
}

ImageIO.getImageReadersByMIMEType() liefert einen Iterator der MIME-Typen für Grafik-Leser.

Die Anfragemöglichkeit gibt es natürlich nicht nur für die Leser, sondern äquivalent auch für die Schreiber. Hier erfüllen die statischen Methoden getImageWritersByFormatName(), getImageWritersBySuffix() und getImageWritersByMIMEType() ihren Zweck.

Die Anfragetypen richten sich bisher nach den Dateiendungen oder MIME-Typen. Diese Aussagen erfordern aber Unterstützung vom Dateisystem oder vom Server. Was ist, wenn eine Grafik über das Netzwerk übertragen wird, die Typinformationen aber fehlen? Dann helfen statische Methoden wie getImageReadersBySuffix() nicht, sondern eine inhaltliche Analyse muss her. Hilfreich ist die Methode ImageIO.reateImageInputStream(), die drei Datengeber analysieren kann: File-Objekte, lesbare RandomAccessFile-Objekte und InputStream-Objekte. Weil die Entwickler nun aber nicht drei unterschiedliche statische Methoden mit unterschiedlichen Parametern für createImageInputStream() vorsehen wollten, nahmen sie die Oberklasse – nämlich Object:

ImageInputStream iis = ImageIO.createImageInputStream( o );

Die Rückgabe ist ein ImageInputStream, der, obwohl er InputStream im Namen trägt, kein Eingabestrom im klassischen Sinne ist. ImageInputStream erlaubt einen Datenzugriff mit wahlfreier Positionierung, und createImageInputStream() ist eine Methode, die den ImageInputStream für eine Datenquelle liefert. Für Benutzer ist ImageInputStream aber immer noch nicht gedacht; Benutzer arbeiten mit ImageReader-Objekten. Ein passendes ImageReader-Objekt für die Bytes liefert getImageReaders():

Iterator = ImageIO.getImageReaders( iis );

Der Iterator liefert alle ImageReader, die das Datenformat für den Binärstrom verarbeiten können. Uns reicht der erste:

if ( iter.hasNext() )
  ImageReader reader = (ImageReader) iter.next();
else
  // Kein Reader, der das Format versteht.

Komprimieren mit »ImageIO« *

Die statischen Methoden ImageIO.write() und ImageIO.read() sind nur Hilfsmethoden, die im Hintergrund einen passenden ImageWriter und ImageReader suchen und ihm die Arbeit überlassen. Während der Kontakt zum tatsächlichen ImageReader eher selten ist, gibt es einen guten Grund, sich mit dem schreibenden ImageWriter näher zu beschäftigen – ihm können über ein ImageWriteParam-Objekt zusätzliche Parameter übertragen werden, etwa der Kompressionsgrad, der sich zwischen 0 und 1 bewegt. JPEG-Bilder sind im Gegensatz zu GIF-Bildern mit Verlust komprimiert, doch lassen sich diese Verluste klein halten. Über eine diskrete Kosinustransformation werden 8 × 8 große Pixelblöcke vereinfacht. Die Komprimierung nutzt die Unfähigkeit des Auges aus, Farbunterschiede so stark wahrzunehmen wie Helligkeitsunterschiede. So können Punkte mit einer ähnlichen Helligkeit, doch einer anderen Farbe zu einem Wert werden. Bei einer hohen Kompression treten so genannte Artefakte auf, die unschön wirken. Bei einer sehr hohen Kompression ist die Bildgröße sehr klein (und die Bilder hässlich). Der Qualitätsfaktor ist vom Typ float und bewegt sich zwischen 0,0 und 1,0. Der Wert 1 bedeutet im Prinzip keine Kompression und somit höchste Qualität. Ein Wert um 0,75 ist ein hoher Wert für Qualitätsbilder, der Wert 0,5 liefert Bilder mittlerer Qualität, und 0,25 sorgt für stärkere Artefakte und hohe Kompression.

Ein Programm, das ein Bild im JPG-Format in eine Datei schreibt, muss zunächst einen Image-Writer erfragen und anschließend den ImageOutputStream aufbauen, um die Daten schreiben zu können. Nach dem Aufbau der Parameter über ein gefülltes ImageWriteParam-Objekt lässt sich das Bild speichern. Zwar verfügt ImageWriter über eine Methode write(RenderedImage), um zum Beispiel ein BufferedImage zu schreiben, doch im Fall der Parameter muss das Bild als IIOImage vorliegen. IIOImage versammelt die Bildinformationen (RenderedImage oder Raster), zusammen mit Vorschaubild und Metadaten.

Bilder in verschiedenen Kompressionsstufen speichern *

Wir wollen nun ein Programm entwickeln, das einen Screenshot nimmt und ihn in den Qualitätsstufen 1,0 bis 0,0 in 0,25er-Schritten komprimiert und das Ergebnis auf dem Bildschirm ausgibt:

Listing 20.23  com/tutego/insel/ui/image/ImageWriterDemo.java

package com.tutego.insel.ui.image;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Locale;
import javax.imageio.*;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.ImageOutputStream;

class ImageWriterDemo
{
  public static void main( String[] args ) throws Exception
  {
    BufferedImage img = new Robot().createScreenCapture(
          new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()) );

    int size = 0;

    for ( float quality = 1f; quality >= 0; quality -= 0.25 )
    {
      ByteArrayOutputStream out = new ByteArrayOutputStream( 0xfff );

      writeImage( img, out, quality );

      if ( size == 0 ) size = out.size();

      System.out.printf( "Qualität: %.1f – Größe: %,.0f k – Verhältnis: %.2f%n",Umbruch
                         quality, (double) out.size() / 1024, Umbruch
                         (double) out.size() / size );
    }
  }

  private static void writeImage( BufferedImage img,
                                  ByteArrayOutputStream out,
                                  float quality ) throws IOException
  {
    ImageWriter writer = ImageIO.getImageWritersByFormatName( "jpg" ).next();
    ImageOutputStream ios = ImageIO.createImageOutputStream( out );
    writer.setOutput( ios );
    ImageWriteParam iwparam = new JPEGImageWriteParam( Locale.getDefault() );
    iwparam.setCompressionMode( ImageWriteParam.MODE_EXPLICIT ) ;
    iwparam.setCompressionQuality( quality );
    writer.write( null, new IIOImage(img, null, null), iwparam );
    ios.flush();
    writer.dispose();
    ios.close();
  }
}

Die Ausgabe des Programms für ein Bild ist in etwa die folgende:

Qualität: 1,0 – Größe: 1.005 k – Verhältnis: 1,00
Qualität: 0,8 – Größe: 339 k – Verhältnis: 0,34
Qualität: 0,5 – Größe: 253 k – Verhältnis: 0,25
Qualität: 0,3 – Größe: 182 k – Verhältnis: 0,18
Qualität: 0,0 – Größe: 77 k – Verhältnis: 0,08

Da der Bildschirminhalt durch die Konsolenausgabe immer etwas anders aussieht, werden natürlich auch die Dateigrößen immer anders aussehen.


Rheinwerk Computing - Zum Seitenanfang

20.6.10 Asynchrones Laden mit getImage() und dem MediaTracker *  topZur vorigen Überschrift

Das Laden von Bildern mittels getImage() der Klasse Toolkit oder Applet wird dann vom System angeregt, wenn das Bild zum ersten Mal benötigt wird. Diese Technik ist zwar recht nett und entzerrt den Netzwerktransfer, eignet sich aber nicht für bestimmte grafische Einsätze. Nehmen wir zum Beispiel eine Animation: Wir können nicht erwarten, die Animation erst dann im vollen Ablauf zu sehen, wenn wir nacheinander alle Bilder im Aufbauprozess gesehen haben. Daher ist zu wünschen, dass zunächst alle Bilder geladen werden können, bevor sie angezeigt werden. Die Klasse MediaTracker ist eine Hilfsklasse, mit der wir den Ladeprozess von Media-Objekten – bisher sind es nur Bilder – beobachten können. Um den Überwachungsprozess zu starten, werden die Media-Objekte dem MediaTracker zur Beobachtung übergeben. Neben diesem besitzt die Klasse gegenüber der herkömmlichen Methode noch weitere Vorteile:

  • Bilder lassen sich in Gruppen organisieren.
  • Bilder können synchron oder asynchron geladen werden.
  • Die Bildergruppen können unabhängig geladen werden.

Bilder dem Cache entnehmen

Eine Webcam erzeugt kontinuierlich neue Bilder. Sollen diese in einem Applet präsentiert werden, so ergibt sich das Problem, dass ein erneuter Aufruf von getImage() lediglich das alte Bild liefert. Dies liegt an der Verwaltung der Image-Objekte, da sie in einem Cache gehalten werden. Für sie gibt es keinen GC, der die Entscheidung fällt: »Das Bild ist alt.« Hier hilft die Methode flush() der Image-Klasse weiter. Sie löscht das Bild aus der internen Liste. Eine erneute Aufforderung zum Laden bringt also das gewünschte Ergebnis.


abstract class java.awt.Image

  • abstract void flush() Gibt die für das Image belegten Ressourcen frei.

Hinweis Image-Objekte werden nicht automatisch freigegeben. flush() entsorgt diese Bilder, macht den Speicher frei und den Rechner wieder schneller.




Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen. >> Zum Feedback-Formular
 <<   zurück
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Java ist auch eine Insel






 Java ist auch
 eine Insel


Zum Katalog: Java SE Bibliotheken






 Java SE Bibliotheken


Zum Katalog: Professionell entwickeln mit Java EE 7






 Professionell
 entwickeln mit
 Java EE 7


Zum Katalog: Einstieg in Eclipse






 Einstieg in
 Eclipse


Zum Katalog: Einstieg in Java






 Einstieg in
 Java


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2011
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.


[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de