Multimedia Core I/O 84 Wie kann ich einfache Strukturen zeichnen? Seit Java2 (JDK 1.2) steht das Java2D-API zur Verfügung. Mit dieser Einführung sind viele erweiterte Grafikmöglichkeiten zur Verfügung gestellt worden, obgleich die Kompatibilität zu den früheren Funktionen gewahrt wurde. Es stellt viele Funktionen zum Zeichnen von grundlegenden, geometrischen Figuren zur Verfügung. Dazu gehören Linien, Rechtecke, Kreise bzw. Ellipsen, Kreisbögen und Polygone. Die entsprechenden Klassen finden sich im Paket java.awt.geom. Sie sind alle Unterklassen der Klasse java.awt.Shape. Damit ist es möglich, sie alle auf die gleiche Art und Weise in den Zeichenmethoden der Klasse Graphics2D zu behandeln. In der Klasse SimpleDraw werden einige grundlegende Figuren gezeichnet, die mit dem Java2D-API sehr einfach zu erstellen sind. GUI Multimedia Datenbank Netzwerk XML RegEx Daten Threads WebServer Applets Abbildung 67: Grundlegende Zeichenfunktionen package javacodebook.media.draw.simple; import java.awt.*; import java.awt.geom.*; import javax.swing.*; public class SimpleDraw extends JPanel{ //In Swing immer die Methode paintComponent überschreiben public void paintComponent(Graphics graphics) { Listing 139: SimpleDraw Sonstiges 330 Multimedia super.paintComponent(graphics); //Graphics-Objekt ist in Wahrheit ein Graphics2D-Objekt Graphics2D g = (Graphics2D) graphics; //Aktuelle Zeichenfarbe setzen g.setColor(Color.black); //Eine Linie zeichnen g.draw(new Line2D.Double(0,100,319,100)); //Ein Rechteck zeichnen g.draw(new Rectangle2D.Double(10, 10, 80, 60)); //Einen Kreis gefüllt zeichnen g.draw(new Ellipse2D.Double(130,10,60,60)); //Eine Ellipse mit Farbverlauf gefüllt zeichnen g.draw(new Ellipse2D.Double(230, 10, 80, 60)); //Ein Rechteck mit abgerundeten Ecken zeichnen g.draw(new RoundRectangle2D.Double(10, 110, 80, 60, 15, 15)); //Einen Kreisbogen zeichnen g.draw(new Arc2D.Double(120, 110, 80, 70, 90, 135, Arc2D.OPEN)); //Ein Tortenstück zeichnen g.draw(new Arc2D.Double(240, 110, 80, 80, 90, 45, Arc2D.PIE)); } //Größe des Panels festlegen public Dimension getPreferredSize() { return new Dimension(320, 200); } //Frame erzeugen Panel anzeigen public static void main(String[] args) { JFrame f = new JFrame(); f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); f.getContentPane().setLayout(new BorderLayout()); f.getContentPane().add(new SimpleDraw(), BorderLayout.CENTER); f.pack(); f.show(); } } Listing 139: SimpleDraw (Forts.) 85 Wie zeichne ich verschiedene Rahmen? Wenn Sie keinen Standard-Rahmen um eine geometrische Figur zeichnen wollen, sondern beispielsweise einen gestrichelten Rahmen, oder einen Rahmen in einer anderen Strichstärke verwenden wollen, so können Sie die Methode setStroke() der Klasse Graphics2D verwenden. In Verbindung mit der Klasse java.awt.BasicStroke Wie zeichne ich verschiedene Rahmen? 331 lassen sich sehr viele Einstellungen für den zu zeichnenden Rahmen vornehmen. Mit ihrer Hilfe können Strichstärke, Linienenden, Linienverbindungen und unterbrochene Linien erzeugt werden. Sie stellt einige Konstanten bereit, über die das Ende und die Verbindung von Linien definiert werden können. Die Werte CAP_BUTT, CAP_ROUND und CAP_SQUARE bestimmen den Stil, in dem ein Linienende gezeichnet wird (gerade, abgerundet oder mit geradem Anhang). Die Werte JOIN_BEVEL, JOIN_MITER und JOIN_ROUND legen fest, wie das Zusammentreffen von zwei Linienenden behandelt wird. Es kann ohne Effekt (BEVEL), mit spitzem Ende (MITER) oder abgerundet (ROUND) gezeichnet werden. Die Klasse BasicStroke bietet verschiedene Konstruktoren an, um die gewünschten Effekte zu erzielen. Die meisten sind sehr einfach zu benutzen. Der Konstruktor für das Erzeugen von gestrichelten Linien ist etwas komplexer. Er hat die Form public BasicStroke(float width, int cap, int join, float miterlimit, float[] dash, float dash_phase). Die Parameter width, cap, join sind leicht erkennbar (Strichstärke und die oben genannten Stile). Miterlimit beschreibt, wie lang zwei Linien am Ende verbunden werden. Das wird wichtig, wenn zwei Linien in sehr spitzem Winkel aufeinander treffen. Wird eine Spitze gezeichnet, so kann diese sehr lang werden. Dies wird durch das miterlimit begrenzt (würde die Spitze länger als der Wert miterlimit, wird stattdessen mit der BEVEL-Funktion gezeichnet). Die Parameter dash und dash_phase beschreiben gestrichelte Linien. Im Array dash werden die Längen der einzelnen Strichabschnitte angegeben. Dabei wird alternierend zwischen gezeichnetem und nicht gezeichnetem Strich gewechselt. Ein Array in der Form {5,5} macht dasselbe wie {5}, da abwechselnd 5 Punkte gezeichnet werden und die nächsten 5 nicht. Der Wert dash_phase dient als Offset für das Zeichnen der Strichelung, d.h. die ersten x Punkte werden übersprungen. Die Klasse StrokeExamples zeigt, wie die verschiedenen Rahmenarten und Linienenden gezeichnet werden. package javacodebook.media.draw.stroke; import java.awt.*; import java.awt.geom.*; import javax.swing.*; public class StrokeExamples extends JPanel{ public void paintComponent(Graphics graphics) { Listing 140: StrokeExamples Core I/O GUI Multimedia Datenbank Netzwerk XML RegEx Daten Threads WebServer Applets Sonstiges 332 Multimedia super.paintComponent(graphics); //Graphics-Objekt ist in Wahrheit ein Graphics2D-Objekt Graphics2D g = (Graphics2D) graphics; //aktuelle Zeichenfarbe setzen g.setColor(Color.black); //Ein Rechteck mit Rahmendicke 5 zeichnen BasicStroke fatBorder = new BasicStroke(5.0f); g.setStroke(fatBorder); g.draw(new Rectangle2D.Double(10, 10, 80, 60)); //Ein Rechteck mit gestricheltem Rahmen zeichnen BasicStroke stroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 1.0f, new float[] {5.0f}, 0.0f); g.setStroke(stroke); g.draw(new RoundRectangle2D.Double(110, 10, 80, 60, 15, 15)); //Rahmen mit verschiedenen Strichlängen zeichnen stroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 1.0f, new float[] {5.0f, 5.0f, 2.0f, 5.0f}, 0.0f); g.setStroke(stroke); g.draw(new RoundRectangle2D.Double(210, 10, 80, 60, 15, 15)); //Linien überschneiden sich am Ende ohne Effekt stroke = new BasicStroke(10.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); g.setStroke(stroke); int x = 25; g.draw(new Line2D.Double(x, 160, x+25, 135)); g.draw(new Line2D.Double(x+25, 135, x+50, 160)); //Linien überschneiden sich am Ende, Spitze wird gezeichnet stroke = new BasicStroke(10.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER); g.setStroke(stroke); x = 125; g.draw(new Line2D.Double(x, 160, x+25, 135)); g.draw(new Line2D.Double(x+25, 135, x+50, 160)); //Linien überschneiden sich am Ende, Spitze wird abgerundet Listing 140: StrokeExamples (Forts.) Wie zeichne ich verschiedene Rahmen? stroke = new BasicStroke(10.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); g.setStroke(stroke); x = 225; g.draw(new Line2D.Double(x, 160, x+25, 135)); g.draw(new Line2D.Double(x+25, 135, x+50, 160)); 333 Core I/O GUI } /** Die gewünschte Größe des Panels festlegen */ public Dimension getPreferredSize() { return new Dimension(300, 180); } /** Einen Frame erzeugen und das Panel anzeigen */ public static void main(String[] args) { JFrame f = new JFrame(); f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); f.getContentPane().setLayout(new BorderLayout()); f.getContentPane().add(new StrokeExamples(), BorderLayout.CENTER); f.pack(); f.show(); } Multimedia Datenbank Netzwerk XML RegEx Daten } Threads Listing 140: StrokeExamples (Forts.) Und so sieht das Ergebnis aus. WebServer Applets Sonstiges Abbildung 68: Verschiedene Möglichkeiten, Rahmen zu zeichnen 334 Multimedia Für die Definition eigener Rahmen muss das Interface java.awt.Stroke implementiert werden, dann können selbst definierte Rahmen innerhalb der Zeichenfunktionen der Graphics2D-Klasse verwendet werden. Der mit Hilfe von setStroke() festgelegte Zeichenstrich kann auf alle Zeichenobjekte angewendet werden, die eine Unterklasse von java.awt.Shape sind. 86 Wie kann ich etwas mit Farbverläufen füllen? Ein Farbverlauf wird mit Hilfe der Klasse java.awt.GradientPaint definiert. Er verläuft von einem Startpunkt hin zu einem Endpunkt, die beide in Koordinatenform angegeben werden. Eine Ausgangs- und eine Endfarbe müssen angegeben werden, dazwischen wird entlang einer Geraden zwischen Start- und Endpunkt ein linearer Farbverlauf berechnet. Mit der Methode fill(Shape shape) aus der Klasse Graphics2D wird der Farbverlauf gezeichnet. Auch zyklisches Füllen ist möglich. Die Klasse GradientPaint hat zwei Konstruktoren, die es in zwei Variationen gibt, einmal mit einzelnen Koordinaten und einmal mit Point2D-Objekten als Parameter, um die Endpunkte der Farbverläufe festzulegen. Wir beschränken uns hier auf die Variante mit einzelnen Koordinaten: public GradientPaint(float x1, float y1, Color color1, float x2, float y2, Color color2); public GradientPaint(float x1, float y1, Color color1, float x2, float y2, Color color2, boolean cyclic); Mit der ersten Variante wird ein einfacher, azyklischer Farbverlauf vom Punkt x1,y1 hin zu Punkt x2, y2 erzeugt. Die zweite Variante ermöglicht sowohl einen azyklischen als auch einen zyklischen Farbverlauf. Mit dem Parameter cyclic kann angegeben werden, ob der Farbverlauf zyklisch wiederholt werden soll. Er wird allerdings nur dann zyklisch verlaufen, wenn der angegebene Endpunkt {x2,y2} nicht auch der Endpunkt des zu füllenden Shapes ist. Wird der Endpunkt {x2, y2} z.B. in der Mitte des zu füllenden Shapes angegeben, so wird der Farbverlauf einmal vom Rand bis zur Mitte von Farbe 1 zu Farbe 2 verlaufen und dann von der Mitte zum anderen Rand von Farbe 2 zu Farbe 1. Das vorherige Bild zeigt die Möglichkeiten, die sich mit GradientPaint ergeben. Wie kann ich etwas mit Farbverläufen füllen? 335 Core I/O GUI Multimedia Abbildung 69: Füllen mit Farbverläufen public void paintComponent(Graphics graphics) { super.paintComponent(graphics); Graphics2D g = (Graphics2D) graphics; float startx = 10, starty = 10; float width=80, height=60; //Farbverlauf definieren und ein gefülltes Rechteck zeichnen //Farbverlauf von links (schwarz) nach rechts (weiß) linear GradientPaint gradient = new GradientPaint(startx, starty, Color.black, startx + width, starty, Color.white); g.setPaint(gradient); g.fill(new Rectangle2D.Double(startx, starty, width, height)); startx = 110; //Farbverlauf diagonal von links oben nach rechts unten gradient = new GradientPaint(startx, starty, Color.black, startx + width, starty + height, Color.white); g.setPaint(gradient); g.fill(new Rectangle2D.Double(startx, starty, width, height)); startx = 20; starty = 110; //Zyklischer Farbverlauf mit Zentrum in der Mitte gradient = new GradientPaint(startx, starty, Color.black, startx + width, starty, Color.white, true); Datenbank Netzwerk XML RegEx Daten Threads WebServer Applets Sonstiges 336 Multimedia g.setPaint(gradient); g.fill(new Rectangle2D.Double(startx, starty, width*2, height)); } 87 Wie kann ich eine Grafik laden und anzeigen? Eine Grafik wird in Java von der Klasse java.awt.Image repräsentiert. Java unterstützt von sich aus die Formate GIF und JPEG. Sie haben mehrere Möglichkeiten, ein Bild zu laden. Innerhalb eines Applets kann die Methode getImage() verwendet werden, in einer Anwendung die Methode getImage() der Klasse java.awt.Toolkit. Dabei kann letztere nur über das Default-Toolkit, das von der Java Runtime bereitgestellt wird, verwendet werden. Das Default-Toolkit wird von der Klasse Toolkit über die Methode getDefaultToolkit() geliefert. Somit sieht ein entsprechender Aufruf in einer Anwendung so aus: Image image = Toolkit.getDefaultToolkit().getImage(dateiname); Die Methode getImage() kehrt sofort zurück und das Laden der Grafik erfolgt im Hintergrund. Dies hat zur Folge, dass ein Bild u.U. noch nicht vollständig geladen ist, wenn es verwendet werden soll. Die Kontrolle über den Ladevorgang können Sie durch den Einsatz eines MediaTrackers (java.awt.MediaTracker) behalten. Im Kapitel über Applets wird dies gezeigt. Wenn Sie das Bild sofort verwenden wollen, nehmen Sie die Klasse javax.swing. ImageIcon. Sie verwendet intern einen MediaTracker und erspart so diverse Codezeilen. Der Konstruktor erhält als Parameter eine Datei oder URL, von der das Bild geladen wird. Mit der Methode getImage() kann das Bild anschließend genutzt werden. Um das Bild anzuzeigen, kann eine entsprechende Swing-Komponente verwendet werden, die ImageIcons unterstützt (z.B. ein JLabel). Es ist jedoch auch sehr einfach, eine eigene Komponente zu schreiben, die Grafiken anzeigt. Eine eigene Komponente kann oft flexibler innerhalb von GUI-Anwendungen eingesetzt werden. Die Klasse ImagePanel ist als Komponente realisiert, die von JPanel erbt. Damit kann sie an beliebiger Stelle verwendet werden, z.B. innerhalb einer JScrollPane. Im Konstruktor wird die Grafik übergeben. Das ImagePanel nimmt danach die Größe der Grafik als seine optimale Größe an. Wie kann ich eine Grafik laden und anzeigen? 337 package javacodebook.media.graphic.load; import java.awt.*; import javax.swing.JPanel; Core I/O public class ImagePanel extends javax.swing.JPanel { //Die Grafik, die angezeigt werden soll private Image image; public ImagePanel(Image image) { this.image = image; } //Grafik auf das Panel zeichnen public void paintComponent(Graphics g) { super.paintComponent(g); g.drawImage(image, 0, 0, image.getWidth(this), image.getHeight(this), this); } GUI Multimedia Datenbank Netzwerk XML RegEx //Größe der Grafik als PreferredSize zurückgegeben public Dimension getPreferredSize() { return new Dimension(image.getWidth(this), image.getHeight(this)); } } Daten Threads Listing 141: ImagePanel WebServer Damit lässt sich ein einfacher Bildbetrachter erstellen. Die Klasse ImageViewer stellt eine einfache Möglichkeit dar, Bilder zu laden und anzuzeigen. Dabei wird der Frame jeweils der Größe der anzuzeigenden Grafik angepasst. Applets package javacodebook.media.graphic.load; import java.awt.*; import java.awt.event.*; import java.io.*; import javax.swing.*; public class ImageViewer extends JFrame { //das ImagePanel ImagePanel imagePanel = null; Listing 142: ImageViewer Sonstiges 338 public ImageViewer() { setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); getContentPane().setLayout(new BorderLayout()); //Einen Button zum Öffnen von Dateien erstellen JPanel buttonPanel = new JPanel(); getContentPane().add(buttonPanel, BorderLayout.NORTH); JButton openButton = new JButton("Datei öffnen"); buttonPanel.add(openButton); //Einen ActionListener auf den Button legen, der einen //FileChooser öffnet openButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { JFileChooser chooser = new JFileChooser(); int status = chooser.showOpenDialog(ImageViewer.this); if (status == JFileChooser.APPROVE_OPTION) { //ausgewählte Datei ermitteln und abspielen File file = chooser.getSelectedFile(); try { ImageIcon icon = new ImageIcon(file.getAbsolutePath()); //vorher vorhandenes ImagePanel entfernen if(imagePanel != null) getContentPane().remove(imagePanel); //das ImagePanel anzeigen imagePanel = new ImagePanel(icon.getImage()); getContentPane().add(imagePanel, BorderLayout.CENTER); //Frame auf die Bildgröße anpassen pack(); } catch(Exception e) { e.printStackTrace(System.out); } } } }); } public static void main(String[] args) { ImageViewer viewer = new ImageViewer(); viewer.pack(); viewer.show(); } } Listing 142: ImageViewer (Forts.) Multimedia