Inhaltsverzeichnis

Java Tutorial #2 - "Splashscreen mit Java"

Autor: 'Kriz'

Wichtiger Hinweis!!!

Die Grundlagen über die Java-Programmierung ist nicht Bestandteil der Tutorialreihe! Zum Lernen von Java empfehle ich das hervorragende Online-Tutorial von Guido Krüger, welches man sich kostenlos von www.javabuch.de runterladen kann.


In diesem Tutorial geht es um die Fortführung von Tutorial #2. Dort haben wir gesehen, wie man schnell und unkompliziert einen Splashscreen in Java realisieren kann. Der Nachteil der dort beschriebenen Technik ist, daß das erzeugte Fenster den gesamten Hintergrund überzeichnet und nur das eigentliche Splashbild angezeigt werden kann. Es ist in Java aber auch möglich, einen Splashbild mit Transparenz bzw. transparenten Bildteilen als Splashscreen anzeigen zu lassen. Man sieht - mehr oder weniger - den aktuellen Bildschirmhintergrund plus die transparenten Bestandteile des Splashbilds. Diese Technik funktioniert allerdings nur mit Applikationen. Reine Applets können die jetzt vorgestellte Technik aus logischen Gründen nicht (fehlender Hintergrund für Transparenzeffekt)! Allerdings kann man bei externen Applets (ein Tutorial hierzu folgt auch bald =) die hier beschriebenen Techniken ebenfalls verwenden.

Zuerst einmal (wieder) etwas graue Theorie:

Von Natur aus überzeichnet jedes Fenster den Bildschirmhintergrund, der sich mit den aktuellen Fenstermaßen überschneidet. Das ist in Java nicht anders wie mit anderen GUI-Gerüsten auch (WinAPI, MFC, QT, WxWindows usw.). Normalerweise wird jedes Basisfenster mit der per Default eingestellten Hintergrundfarbe gezeichnet. In Java wird ein nacktes Window-Fenster mit einem hellen Grau gezeichnet, daß immer zu sehen ist, sofern nicht später diverse Fensterkomponenten den Fensterhintergrund überzeichnen.

Theoretisch gibt es zwei vernünftige Ansätze, wie man den überzeichneten Bildschirmhintergrund durch das Fenster hindurchscheinen lassen kann, um einen Transparenzeffekt zu erzeugen:

  1. Das GUI-Gerüst selbst ist von Haus aus in der Lage, transparente Effekte auf seine Fenster zu realisieren.
  2. Man trickst den Benutzer des Fenster dahingehend aus, daß man einfach den Hintergrund ausschneidet (grabbing) und ihn in das Fenster kopiert.

Der erste Punkt ist heikel und wird nicht von allen GUIs unterstützt! Bis vor 2 bis 3 Jahren war die GUI von Windows dazu nicht in der Lage. Erst die modernen Windows-Betriebssysteme (Windows 2000 und XP) haben Transparenzeffekte direkt in ihrem Gerüst implementiert. Die ersten Gehversuche zeigten sich schon voher im Internet-Explorer 5. Dort konnte man mittels Filtern Webseiten gegenseitig faden lassen.

Mittlerweile können Windows, KDE 3.x und andere GUIs ganz gut Transparenzeffekte darstellen, aber das Java eine umfassend portierbare Sprache ist, müssen auch diejenigen GUIs bedient werden können, die keine Transparenz von Haus aus mitbringen! Daher fällt dieser Punkt vollkommen weg aus unserer Betrachtung!

Der zweite Punkt ist dagegen eine Art Universallösung für alle GUIs. Das Grabben des aktuellen Bildaschirms und das darauffolgende Kopieren des Bildes als Hintergrund für das Fenster lassen die Illusion entstehen, daß kein Fenster den Bildschirm überzeichnet hat. Auf dieser Basis kann man nun ein zweites Bild mit transparenten Eigenschaften über den Fensterhintergrund zeichnen und voilá: Man hat den Eindruck, daß das Splashbild alleine ganz cool auf dem Bildaschirm mit seiner Transparenz angibt :)

Da aber Transparenz nicht von jedem Bildformat unterstützt wird, muß man also sein Splashbild vorher schon in das richtige Format konvertieren. Die zwei einzigen von Java akzeptierten Bildformate, die Transparenz unterstützen, sind GIF und PNG. GIFs Transparenz reduziert sich lediglich auf das Ausschneiden einer einzigen Farbe aus seiner Farbpalette. PNGs Transparenz dagegen arbeitet auf zwei Gleisen: Erstens wie GIF mit nur einer Transparenzfarbe und zweitens mit einem 8 Bit breiten Alphakanal auf alle verfügbaren Bildpixel. Daraus ergibt sich folgende Aufstellung der verfügbaren Formate:


Für einfache Transparenz reicht demnach also GIF oder das 8 Bit PNG Format. Für professionelle und coolere Effekte muß es dagegen schon das 24 Bit PNG Format sein! Moderne Grafikbearbeitungsprogramme wie PaintShop Pro 7, Photoshop 6/7 oder Corel Photopaint 10 (evtl. auch Gimp, daß weiß ich jetzt nicht genau) können 24 Bit PNGs konvertieren und speichern. Wie man nun diese nette Eigenschaft der Transparenz auf sein Splashbild anwendet, bleibt natürlich dem Künstler selbst überlassen und unterliegt der freien Kreativität. Man sollte nur darauf achten, daß nicht jede Transparenz auf jedem Bildschirmhintergrund gleich gut aussieht! Daher ist mein Rat an alle angehenden Malermeister: Weniger ist oft mehr…

Kommen wir nun zu der Implementierung unseres transparenten Splashscreens. Die Vorgehensweise ist absolut identisch zum Tutorial #2 (Splashscreen). Der signifikante Unterschied ist der, daß wir eine Toolklasse von Java benutzen, die sich Robot schimpft. Die Klasse Robot besitzt eine Methode createScreenCapture(), die als Parameter ein Objekt der Klasse Rectangle erwartet. Diese wiederum definiert ein 2D-Rechteck mit Startkoordinaten x/y für den linken, oberen Nullpunkt und Angaben zur Breite und Höhe des Rechtecks. Wenn man nun den Splashscreen mittig zentriert anzeigen will, dann übergibt man die exakten Positions- und Größenangaben des Window Objekts an die Instanz von Rectangle. createScreenCapture() liefert nach erfolgreicher Arbeit ein Objekt der Klasse BufferedImage zurück, die von Image abgeleitet worden ist. Die Erweiterungen von BufferedImage interessieren hierbei absolut nicht, da wir nur das in der Klasse eingebettete Unterobjekt Image benötigen.

Alles weitere ist Kinderkram: Wir zeichnen zuerst den gegrabbten Bildschirmausschnitt mittels paint() und danach darüber unser Splashbild. Java sorgt selber dafür, daß das Capture- und Splashbild bildtechnisch korrekt überlagert werden. So entsteht der Eindruck, daß unser transparentes Splashbild direkt auf dem aktuellen Bildschirmhintergrund gezeichnet worden ist.

Das Codegerüst

Wir erstellen eine neue Datei für unsere TransparentSplashScreen Klasse und implementieren folgenden Java Code:

// TransparentSplashScreen.java
 
/* Eventuell hier erst das Paket definieren, falls erforderlich! */
 
import java.awt.*;
import java.awt.image.*;
 
public class TransparentSplashScreen
extends Window
{
    private Image        m_imageSplash = null,
                         m_imageCapture = null;
    private Robot        m_robot = null;
    private Rectangle    m_rect = null;
    private MediaTracker m_mediaTracker = null;
 
    public TransparentSplashScreen(String strImagePath, Dimension dimImageSize)
    {
        super(new Frame());
 
        setLocation((getToolkit().getScreenSize().width - dimImageSize.width) / 2,
                    (getToolkit().getScreenSize().height - dimImageSize.height) / 2);
        setSize(dimImageSize);
 
        if(strImagePath!=null && dimImageSize!=null)
        {
            m_rect = new Rectangle(getLocation().x, getLocation().y, getSize().width, getSize().height);
 
            try
            {
                m_robot = new Robot();
                m_imageCapture = m_robot.createScreenCapture(m_rect);
            }
            catch(AWTException e)
            {
            }
 
            m_imageSplash = getToolkit().getImage(strImagePath);
            m_mediaTracker = new MediaTracker(this);
            m_mediaTracker.addImage(m_imageSplash, 0);
            m_mediaTracker.addImage(m_imageCapture, 1);
 
            try
            {
                m_mediaTracker.waitForAll();
            }
            catch(InterruptedException e)
            {
            }
        }
    }
 
    public void paint(Graphics g)
    {
        g.drawImage(m_imageCapture, 0, 0, m_imageCapture.getWidth(), m_imageCapture.getHeight(), this);
        g.drawImage(m_imageSplash, 0, 0, m_dimImageSize.width, m_dimImageSize.height, this);
    }
 
    public void start()
    {
        setVisible(true);
    }
 
    public void destroy()
    {
        setVisible(false);
        dispose();
    }
}

Die wesentlichen Grundzüge gleichen der der Klasse SplashScreen. Die Unterschiede sind die, daß wir erstens noch das Paket java.awt.image.* importieren müssen (wegen der Klasse BufferedImage) und zweitens eine zweite Image Instanz für den Bildschirmausschnitt brauchen, die Robot Instanz und eine Rectangle Instanz. Im Konstruktor wird daher zuerst die Variable m_rect von Rectangle erzeugt und die exakten Fensterdaten unseres Window Objekts als Parameter übergeben. Damit haben wir zuerst einmal die Basisdaten für den zu grabbenden Bildschirmausschnitt (im folgenden Capturebild genannt).

Danach beginnt ein try/catch Block für die Erzeugung der Robot Instanz m_robot. Diese wirft nämlich beim Scheitern eine Ausnahme des Typs AWTException und diese müssen wir abfangen. Innerhalb des try/catch Blocks rufen wir nach der Instanzierung von m_robot dessen Methode createScreenCapture() auf und übergeben ihr als Parameter die Variable m_rect mit den Ausschnittsdaten. Falls alles gut verläuft, liefert die methode wie bereits erwähnt ein Objekt vom Typ BufferedImage zurück. Das dort enthaltene Subobjekt Image ist dagegen für uns relevant, sonst nichts. Dieses wird in m_imageCapture gespeichert.

Der Rest gleicht dem Code von SplashScreen, außer daß wir mittels dem MediaTracker neben dem normalen Splashbild auch noch das Capturebild precachen lassen. Dafür vergeben wir dem Capturebild die Bildnummer 1. Das waren die Vorgänge innerhalb des Konstruktors unserer Klasse. Die Klassenmethode paint() muß darüberhinaus noch wie folgt abgeändert werden:

public void paint(Graphics g)
{
    g.drawImage(m_imageCapture, 0, 0, m_imageCapture.getWidth(), m_imageCapture.getHeight(), this);
    g.drawImage(m_imageSplash, 0, 0, m_dimImageSize.width, m_dimImageSize.height, this);
}

Zuerst wird das Capturebild gezeichnet, erst danach das eigentliche, transparente Splashbild. Die Angaben des Capturebilds bezüglich von Breite und Höhe entnehmen wir durch direktes Aufrufen der beiden Image Methoden getWidth() und getHeight().

Und das war's auch hier schon wieder =)

Das Testprogramm

In Aktion kann man unsere neue TransparentSplashScreen-Klasse nun mit folgendem Testprogramm sehen. Dabei liegt die TransparentSplashScreen-Klasse im selben Verzeichnis wie die Hauptfunktion, d.h. wir benötigen keinen expliziten Import der TransparentSplashScreen-Klasse:

// Testprogramm für TransparentSplashScreen-Klassen
 
import java.awt.*; // Für Klasse Dimension
 
public class TransparentSplashTest
{
    private TransparentSplashScreen m_transparentSplashScreen = null;
    private final String            m_strImagePath = "intro.png";
    private final Dimension         m_dimImageSize = new Dimension(400, 300);
 
    public static void main(String[] strArgs)
    {
        TransparentSplashTest tsplash = new TransparentSplashTest();
    }
 
    public TransparentSplashTest()
    {
        m_transparentSplashScreen = new TransparentSplashScreen(m_strImagePath, m_dimImageSize);
        m_transparentSplashScreen.start();
 
        try
        {
            Thread.sleep(5000);   // 5 Sekunden anzeigen lassen
        }
        catch(InterruptedException e)
        {
        }
 
        m_transparentSplashScreen.destroy();
 
        System.exit(0);
    }
}

Bei Fragen bezüglich des restlichen Codegerüsts solltet ihr das Tutorial #2 lesen. Dort wird die ursprüngliche SplashScreen-Klasse erklärt.

Flackern beim Anzeigen verhindern

Eventuell ist es schonmal jemandem aufgefallen, daß der Splashscreen flackert beim Anzeigen auf dem Bildschirm. Der Grund ist der, daß zwei Bilder sehr zeitnah auf die aktuelle Fensterfläche gezeichnet werden (siehe Methode paint()). Das ist selbst für Java unter schnellen Rechnern etwas viel. Abhilfe schafft man, indem man den Fensterinhalt rendern läßt, ehe er gezeichnet wird. Dazu gibt es eine sehr einfache und einleuchtende Methode:

Man zeichnet einfach den Bildschirmausschnitt und das transparente Bild in ein neues, temporäres Bild, welches dann anschließend von paint() gezeichnet wird. Dadurch beschleunigen wir zwar den Bildaufbau nicht, aber wir vermeiden konsequent das lästige Flackern beim Zeichnen. Dazu ändern wir also einfach die paint() Methode wir folgt ab:

public void paint(Graphics g)
{
    Image imgTemp = createImage(m_dimImageSize.width, m_dimImageSize.height);
    Graphics gfxTemp = imgTemp.getGraphics();
 
    gfxTemp.drawImage(m_imageCapture, 0, 0, m_imageCapture.getWidth(this), m_imageCapture.getHeight(this), this);
    gfxTemp.drawImage(m_imageSplash, 0, 0, m_dimImageSize.width, m_dimImageSize.height, this);
 
    g.drawImage(imgTemp, 0, 0, imgTemp.getWidth(this), imgTemp.getHeight(this), this);
}

Zuerst erstellen wir ein Image Objekt. Dann rufen wir die Methode createImage() der Klasse Graphics auf, welche wir über die Variable g vom Programm erhalten haben. Als Parameter erwartet createImage() die Breite und die Höhe des neuen Bildes. Wir übergeben hier einfach die Angaben unseres transparenten Hintergrundbildes. Danach erstellen wir uns ein temporäres Graphics Objekt und weisen ihm via Methode getGraphics() den Grafikkontext unseres neuen Bildes zu. Nur so können wir die vorhandenen Bilder auf das neue Bild rendern lassen. Anschließend zeichnen wir nacheinander wie gehabt die beiden Bilder, allerdings nicht auf den Grafikkontext unseres Programms, sondern auf den temporären Grafikkontext des neuen Bildes! Zum Schluß lassen wir einfach das neue Bild vom Programm-Grafikkontext auf unser Fenster zeichnen. Da das temporäre Bild bereits vollständig vorgerendert worden ist, muß Java nur ein anstatt zwei Bilder zeichnen.

Voilá - Das Flackern ist verschwunden :-)

Tipps

Siehe Tutorial #2.

Anmerkung

Dieses Tutorial stammt aus der ehemaligen Sammlung des resourcecode.de und konnte dank der freundlichen Zustimmung des Autors in das thewall-Wiki übertragen werden.