Java Grafikprogrammierung

Willemers Informatik-Ecke

Das einzig seriöse Javabuch :-) Mehr...

Errata

Bei Amazon bestellen


2014-05-19
Die Grafikausgabe erfolgt nicht, wie das Malen auf eine Leinwand. Eine Swing-Anwendung wird seine Grafik dann erzeugen, wenn es vom System dazu aufgefordert wird. Der Hintergrund ist, dass in einer Fensterumgebung eine Anwendung leicht mal von einem anderen Fenster überdeckt wird. Wird es dann wieder nach vorn geklickt, muss es den Fensterinhalt wieder aufbauen.

Die JFrame-Klasse enthält eine Methode paint, die aufgerufen wird, wenn der Fensterinhalt neu gestaltet werden muss. Eine Applikation, die selbst grafisch tätig wird, überschreibt diese Methode.

Als Parameter erhält sie ein Objekt vom Typ Graphics2D, das aus Kompatibilitätsgründen Graphics heißt. Diese stellt quasi den Zeichenhintergrund dar, deren Methoden die Zeichenprimitive sind.

Beispiel:

Das folgende Beispiel malt ein gelbes Oval, das sich in der Größe am Fenster orientiert. Innerhalb des Ovals malt die Anwendung einen Text, indem die Größe des Zeichenbereichs in Pixeln angegeben wird.

In main wird das eine Instanz der eigenen Klasse erzeugt. Daraufhin erledigt der Konstruktor die Arbeiten am Rahmenfenster. Er setzt die CloseOperation, setzt den Titel und die Größe und macht es schließlich sichtbar. Das wirklich Spannende passiert in der überschriebenen Methode paint.

import javax.swing.*;
import java.awt.*;

public class MyGraphics extends JFrame {

  public static void main(String[] args) {
    MyGraphics fenster = new MyGraphics();
  }

  public MyGraphics() {
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setTitle("Zeichnen mit Java");
        setSize(400, 300);
        setVisible(true);
  }

  @Override public void paint(Graphics g) { // @Override ermöglicht dem Compiler die Kontrolle
    Insets insets = getInsets();
    int originX = insets.left;
    int originY = insets.top;
    int breite   = getSize().width  - insets.left - insets.right;
    int hoehe   = getSize().height - insets.top  - insets.bottom;
    g.setColor(Color.yellow);
    g.clearRect(originX, originY, breite-1, hoehe-1);
    g.fillOval(originX, originY, breite-1, hoehe-1);
    g.setColor(Color.black);
    String meldung  = "" + breite + " x " + hoehe + " Pixel";
    g.drawString(meldung, breite/2, hoehe/2);
  }
}

Die Methoden, die mit draw beginnen, wie beispielsweise drawRect für das Zeichnen eines Rechtecks, zeichnen die Umrisse, während die Methoden, die mit fill beginnen, wie beispielsweise fillRect, das innere der Figur ausfüllen.

Linien, Rechtecke und Polygone

Kreise, Ellipsen, Ovale und Kreisausschnitte

Ein Kreis ist eigentlich nichts anderes als ein Oval, dessen Höhe und Breite gleich sind. Darum gibt es in Java auch keine eigene Funktion zum Zeichnen eines Kreises. Wenn man sehr pedantisch ist, ist das Oval von Java auch eigentlich eine Ellipse. Da die Ellipse aber ein Spezialfall des Ovals ist, ist die Bezeichnung Oval zumindest nicht falsch.

Texte malen

Um einen Text in eine Grafik zu malen, verwenden Sie die Methode drawString. Als Paramter übernimmt sie den String und die Koordinaten.

public class Zeichensatz extends JFrame {
  public static void main(String[] args) {
    Zeichensatz fenster = new Zeichensatz();
    fenster.setTitle("Zeichensatzinfo mit Java");
    fenster.setSize(400, 300);
    fenster.setVisible(true);
  }

  public Zeichensatz() {
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  }

  public void paint(Graphics g) {
    Insets insets = getInsets();
    int originX = insets.left;
    int originY = insets.top;
    int breite   = getSize().width  - insets.left - insets.right;
    int hoehe   = getSize().height - insets.top  - insets.bottom;
    g.clearRect(originX, originY, breite-1, hoehe-1);
    g.setColor(Color.black);
    g.setFont(new Font("FreeSerif", Font.PLAIN, 16));
    FontMetrics fm = g.getFontMetrics();
    int zeile = 1;
    String meldung  = "Bildschirm: " + breite + " x " + hoehe + " Pixel";
    g.drawString(meldung, originX, originY+zeile*fm.getHeight());
    zeile++;
    meldung = "Zeilenhöhe - getHeight(): " + fm.getHeight();
    g.drawString(meldung, originX, originY+zeile*fm.getHeight());
    zeile++;
    meldung = "Ascent - getAscent(): " + fm.getAscent();
    g.drawString(meldung, originX, originY+zeile*fm.getHeight());
    zeile++;
    meldung = "Descent - getDescent(): " + fm.getDescent();
    g.drawString(meldung, originX, originY+zeile*fm.getHeight());
    zeile++;
    meldung = "Durchschuss - getLeading(): " + fm.getLeading();
    g.drawString(meldung, originX, originY+zeile*fm.getHeight());
    zeile++;
    meldung = "Breite von \"Willemer\" - stringWidth(\"Willemer\"): " + fm.stringWidth("Willemer");
    g.drawString(meldung, originX, originY+zeile*fm.getHeight());
  }
}

Images

Bei vielen Spielen benötigt man Spielfiguren. Aber auch, wenn man Fotos in einem Programm darstellen will, wird man bei den Images landen. Ein Image wird aus einer Bilddatei gewonnen. Diese Bilddatei kann entweder vom Dateisystem geladen werden oder beispielsweise bei Spielen in der jar-Datei mit eingebunden werden.

Es muss das Package java.awt.Image eingebunden werden.

import java.awt.Image;

Zunächst wird ein Objekt der Klasse Image benötigt. Dieses muss natürlich mit einem Bild gefüllt werden. Im ersten Schritt wird also eine Bilddatei einem Objekt der Klasse Image zugeführt.

Die Applet-Klasse verfügt über die Methode getImage und kann eine Bilddatei direkt laden. Bei Applets kann eine URL für das Bild verwendet werden. So lädt die folgende Zeile ein Bild von einer Website.

Image img = getImage("http://www.seite.de/bild.jpg");

Falls die Bilder aber an der gleichen Stelle stehen wie die Anwendung, kann das Applet auch getCodeBase() einsetzen.

Image img = getImage(getCodeBase(),"meinbild.jpg");

Eine JFrame-Anwendung muss das Standard-Toolkit bemühen, um getImage aufzurufen.

Image img = Toolkit.getDefaultToolkit().getImage("meinbild.gif");

Nun verfügt die Anwendung über ein Image, das sie in einer Graphics-Umgebung mit der Methode drawImage darstellen kann.

paint(Graphics g)  {
   ...
   g.drawImage(img, xPos, yPos, this);

Dieser Aufruf wird das Image img darstellen. Die Position wird durch xPos und yPos bestimmt. Das this im vierten Parameter bezieht sich auf den JFrame und informiert das Programm über den Status des Bildes. Aber auch in einem Applet passt ein this.

Eigentlich ist dieser Parameter vom Typ ImageObserver. Diese abstrakte Klasse wird von der Klasse Component implementiert. Insofern ist this an dieser Stelle fast immer richtig.

Neben der Möglichkeit, Images anzuzeigen, können auch Rechteckbereiche des Bildschirms copyArea kopiert werden.

Clipping

Man kann nicht überall malen. Beispielsweise muss man einen Rand freihalten oder möchte verhindern, dass in den gerade mühsam gezeichneten Bereich hineingemalt wird. Der Programmierer könnte natürlich ausrechnen, ob sich die gemalte Figur vielleicht außerhalb des Zielgebietes ausdehnt. Aber es geht auch einfacher, indem ein Clipping-Bereich definiert wird, außerhalb dessen jegliche Malerei unterbunden wird.

Modernisierung: Graphics2D

Die Grafikfähigkeiten in Java sind durch die 2D-Bibliothek erweitert worden.

Die alte Schnittstelle wurde so erhalten, dass alle bisherigen Methoden auch bei 2D verwendbar sind. Sogar die Methode paint wurde beibehalten. Allerdings verbirgt sich nun hinter dem Parameter Graphics ein Graphics2D-Objekt, dass man innerhalb der ersten Zeile einfach per casting verwenden kann. Wenn man die ersten beiden Zeilen der Methode paint auf diese Weise anpasst, kann man den Rest der Methode einfach belassen.

public void paint(Graphics pg) {
    Graphics2D g = (Graphics2D) pg;

Die bisherigen Methoden arbeiten. Aber nun können die Ergänzungen von 2D hinzugefügt werden.

Das Konzept ändert sich dahin, dass bei 2D grafische Objekte eingeführt werden. Linien beispielsweise sind nun nicht mehr nur die Koordinaten, die die drawLine-Methode als Parameter erhält, sondern eigenständige Objekte, die sich von der Klasse Line2D ableiten. Ein Linienobjekt wird angelegt, erhält beim Konstruktoraufruf seine Koordinaten und wird dann der überladenen Methode draw oder fill übergeben.

Durch diese Veränderung des Konzepts können nun die Linien direkt nach dem Schnittpunkt mit einer anderen Linie gefragt werden.

Die Grundprimitive heißen Line2D, Point2D, Rectangle2D, RoundRectangle2D, QuadCurve2D, Ellipse2D, Arc2D und sind abstrakte Klassen. Die implementierten Klassen der Klasse Line2D lauten Line2D.Double und Line2D.Float.

Die Positionen werden den Objekten über den Konstruktor mitgegeben. Dann kann das Objekt einfach der Methode draw oder fill übergeben werden.

Die Positionen werden nicht mehr als ganzzahlige Werte behandelt, sondern als float oder double. Der Hintergrund ist, dass so eine Abstraktion von der tatsächlichen Hardware erreicht wird. Ein Bildschirm hat eine Auflösung von vielleicht 70 dpi. Aber auf Papier ist diese Auflösung wesentlich höher. Wird eine diagonale Linie auf die Bildschirmauflösung reduziert, hat sie bei der Papierausgabe bereits sichtbare Ecken.

Wie bisher kann die Methode setColor verwendet werden, um die Farbe festzulegen. Neu ist die Möglichkeit Paint zu verwenden.

g.setPaint(new GradientPaint(0, 0, Color.blue, 50, 25, Color.green, true));

Mit Stroke kann die Dicke eines Striches verändert werden.

g.setStroke(new BasicStroke(5));

Die Strichstärke ist nun 5.


Homepage - Java (C) Copyright 2011, 2014 Arnold Willemer