Java applet Tutorial

Bilder anzeigen und verändern via MemoryImageSource

1.1 Applet und Thread erzeugen

Für das Applet müssen wir eine neue Klasse erzeugen, abgeleitet von java.applet.Applet (welche abgeleitet ist von java.awt.panel, d.h. es ist ein Container) mit der Schnittstelle Runnable (wird nicht unbedingt für das Applet benötigt, jedoch für den Thread). Ein Applet benötigt im Gegensatz zu einer normalen Applikation keine main()-Methode, sondern die init() und start() Methoden regeln die Ausführung des Applets. Außerdem müssen wir noch ein paar Klassen importieren, zum Laden und Bearbeiten des Bildes.

import java.awt.*
import java.awt.image.*

public class bild extends java.applet.Applet implements Runnable{
 
:                                        //Appletquelltext
}

Als nächstes benötige wir ein paar Variablen, einerseits für das Zoomen, andererseits die für das Bild benötigten Speicherstrukturen.

int width=216;                                                //Breite de Bildes
int height=288;                                               //Höhe des Bildes
int zoomfaktor=3;                                          //bestimmt die Tiefe des Zooms
int zstart=1<<zoomfaktor;                           //Zoomstartwert
int zstop=zstart*3                                          //Wert der angibt, wann der Zoom stoppen soll
boolean quit=false;                                        //wenn quit==true wird die Ausführung des Applets                                                                           gestoppt

Image zoomimage;                                        //hierrein wird das Bild geladen
Image display;                                               //Buffer für die Ausgabe

int zoomimageAr[]=new int [width*height];   //Das Bild gespeichert in einem 1-dimensionalen Feld
int displayAr[]=new int [width*height];           //Feld für die Bildschirmausgabe

MemoryImageSource mic;                           //sorgt für die Zuordnung des Feldes zum Bild

Thread hThread=null;                                 //der Thread

Den Thread erzeugen wir in der Methode start(), die aufgerufen wird, wenn das Applet zum ersten mal angezeigt wird und wenn es gestoppt wurde und dann weiterläuft (z.B. das Applet wird vom Browser wieder reingescrollt). Die Methode stop() beendet die Ausführung (z.B. wenn der Browser geschlossen wird), hier wird der Thread gestoppt.

public void start(){
  if (hThread==null){
    hThread = new Thread(this);
    hThread.start();
  }
}

public void stop(){
  if (hThread != null){
    hThread.stop();
    hThread=null;
  }
}

1.2. Initialisierung

Einen Konstruktor ist nicht nötig, da wir alle Initialisierungen in init() vornehmen, diese wird immer aufgerufen, sobald das Applet vom Browser geladen wird. Da wir bei Applets nicht vorhersage können, wann die nötige Ressource (Bilder, Sound,...) fertig geladen sind, müssen wir die zu ladenden Objekte mit einem MediaTracker überwachen.Die Methode getCodeBase() gibt den Pfad des Applets zurück, damit wir unser zu ladendes Bild nicht global angeben müssen. Mit getImage(URL, string) können wir ein Bild in das Applet laden, wobei getimage (URL, string) gif und jpeg Bilder verarbeiten und laden kann.
Um aus dem Image die einzelnen Pixel mit den Farbinformationen zu kriegen wird das Bild mit einem Pixelgrabber als Kopie in einem Array gespeichert. Das Array muß vom Typ int sei, da die Bildinformationen aus 4*8 Bit pro Pixel dargestellt werden, wobei die obersten acht Bit den alpha wert repräsentieren (ist bei geladenen Bildern immer 0xFF, also undurchlässig). Via eines Objekt vom Typ  MemoryImageSource werde wir das Array nach der Veränderung wieder in ein Image konvertieren.

public void init()
{
  MediaTracker tracker = new MediaTracker(this);
  showStatus("Loading Image...");                                         //Zeigt in der Status-leiste "Lo..." an.
  zoomimage = getImage(getCodeBase(),"razor.jpg");           //Laden des Bildes
  tracker.addImage(zoomimage,0);                                          //Fügt das Bild zum Mediatracker hinzu
  try
  {
    tracker.waitForID(0);}                                                       //Warten bis das Bild vollständig                                                                                                  //geladen ist
  catch (Exception e){
    showStatus("Error!!!!!!");
  }
  resize(width, height);
  makear();                                                                               //Macht das Bild zum Array
  mic=new MemoryImageSource (width, height, displayAr, 0, height);
  showStatus(" ");
}

public void makear(){
  PixelGrabber Grabber=new PixelGrabber(zoomimage.getSource(), 0, 0, width, height,       zoomimageAr, 0, width);
  try{
    Grabber.grabPixels();
  }
  catch (Exception e){
    return;
  }
}

1.3 Die Berechnung

Die Methode run() wird vom Thread als Startmethode aufgerufen und enthält den eigentlichen Laufzeitcode. Viel mache wir hier nicht, das neue Bild wird in ZoomIn (int) berechnet und dann via repaint() dargestellt. Nach der Darstellung schläfern wir den Thread für 30 Ms ein, damit es nicht so schnell läuft, im nächsten Tutorial, werden wir eine Zeitmessung machen, um diesen Wert besser einzustellen (damit es auf allen Computern ungefähr gleich schnell läuft). Ein kleines Manko an Java ist, daß es sich selbständig um die Verwaltung des Speichers kümmert und manche Methoden und Objekte damit nicht gerade sparsam umgehen. Der GarbageCollector arbeitet in den Pausen. Da er aber nach meinen Erfahrungen die Pause nicht so richtig nutzt und das ganze Applet irgendwann furchtbar anfängt zu ruckeln und man das Gefühl hat, er formatiert die Festplatte rufen wir den gc() selber auf nach einem vollen Zoom und vermeiden so, daß der Speicher zu voll wird.
ZoomIn(int) führt die eigentlichen Berechnungen aus, die ein wenig optimiert sind. Die Methode ist synchronized, ebenso wie paint(), damit verhindern wir, daß gleichzeitig das Bild angezeigt und wieder neu berechnet wird (ergibt Streifen bei der Anzeige). Damit die neu errechnete Ausgabe, die in das displayAr geschrieben wird auf den Bildschirm korrekt ausgegeben werden kann, wird das Array durch createImage (MemoryImageSource) in ein Image Objekt kopiert. Hier kommt noch mal der ganze Weg des Bildes bis zur Ausgabe:

getImage PixelGrabber Berechnung Memory-
razor.jpg --------> zoomimage -----------> zommimageAr ----------> displayAr --- Image-
    | Source
auf den Bildschirm <----------- display <---------- mic <--
drawImage createImage      

public void run(){
  int zaehler=1;
  int n= zstart;

  while (!quit){
    if (n==zstop) zaehler =-1;
    if (n==zstart) zaehler =1;
    n+=zaehler;
    ZoomIn (n);
    repaint();
    try{
      Thread.sleep(30);
    }
    catch (Exception e){}
    if (n==zstart) System.gc();
  }
}

public synchronized void ZoomIn (int n){
  int x=0;
  int y=0;
  int xbuf=0;
  int ybuf=0;
  int i2buf = 0;

  int mult216[]=new int [height];                                  //ein Array, um eine Multiplikation zu sparen
  for (int i=0;i<height;i++) mult216[i]=i*width;
  xbuf=((n-zstart)*11)<<(zoomfaktor);
  ybuf=((n-zstart)*11)<<(zoomfaktor);

  for(int i2=0;i2<=height-1;i2++){
    x = mult216[(((i2<<zoomfaktor)+xbuf))/n];
    i2buf=mult216[i2];

    for(int i1=0;i1<=width-1;i1++){
      y =(((i1<<(zoomfaktor))+ybuf))/n;
      displayAr[i2buf+i1]=zoomimageAr[x+y];}}

  display = createImage(mic);
}

1.4 Die Bildschirmausgabe

Damit unser nun neu errechnetes Bild schnell auf den Bildschirm kommt, werden die paint(Graphics) und die update(Graphics) Methoden überschrieben, die paint(Graphics)-Methode ist ja logisch, die update(Graphics) Methode ist wichtig, weil Java dort normalerweise erst ein löschen des Bildschirmbereichs vorsieht und das flimmert und ist unglaublich langsam. Da wir eh den ganze Bereich überschreiben muß da auch vorher nichts gelöscht werden.

public synchronized void paint(Graphics g)
{
  g.drawImage(display,0,0,this);

}
public void update(Graphics g)
{
  paint (g);
}

1.5 Der Rest

Der Rest ist nur zum Überprüfen des Mauszustandes.

public boolean mouseEnter(Event e, int x, int y){
  showStatus("Erster Test von Boris");
  return true;
}

public boolean mouseExit(Event e, int x, int y){
  showStatus(" ");
  return true;
}
public boolean mouseDown(Event e,int x, int y){
  if (!quit){
    quit=true;
    showStatus("Halt");
  }
  return true;
}

1.6 Nachwort und Vorschau

So, das war´s. Jetzt ist der tolle Zoomer fertig. Der Algorithmus zum Zoomen ist wahrscheinlich nicht besonders gut, ich habe mir da nie soviel Gedanken gemacht, weil das eh immer nur zum Testen ist. Auch die Bildschirmausgabe via MemoryImageSource ist noch nicht das Ultimum, man sieht ja in dem Graphen oben, wieviel Schritte man machen muß um das fertige Ergebnis zu sehen. Das kann natürlich nicht besonders schnell sein. Im nächsten Tutorial werde ich eine andere Möglichkeit vorstellen und bald auch zeigen, wie man die gute alte 8Bit Palette benutzt. Für einfache Effekte reicht diese Methode jedoch aus. Ich bin für alle Anregungen und Verbesserungen dankbar, da auch meine Java Kenntnisse noch lange nicht perfekt sind.

©©©©18.Juli.0 Tobias von Loesch