Axel Rogat
Objektorientiertes Programmieren mit C++ und JAVA
 
30.4: Font Kapitel 30 31: Das AWT 
 
  30.5 Image  
 

Es gibt eine Vielzahl ausgefeilter Möglichkeiten, fertige Bilder in ein Applet einzubauen -- beispielsweise stückweise, während sie noch über das Netz geladen werden, während eine Kamera sie übermittelt, etc. Wir werden uns zunächst nur mit einfachen Versionen begnügen.

Image-Zeichenfunktionen in Graphics
boolean drawImage(Image, int x, int y, ImageObserver)
  Bild zeichnen
boolean drawImage(Image, int x, int y, intwidth, int height, ImageObserver)
  Bild zeichnen, angepaßt auf die angegebene Breite/Höhe
boolean drawImage(Image, int x, int y, Color bgcolor, ImageObserver)
  Bild zeichnen, noch ungeladene Teile mit bgcolor ausfüllen
boolean drawImage(Image, int x, int y, int width, int height, Color bgcolor, ImageObserver)
  Bild zeichnen, angepaßt und mit bgcolor aufgefüllt
boolean drawImage(Image, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver)
  Bildausschnitt (sx1,sy1)-(sx2,sy2) angepaßt in das Rechteck (dx1,dy1)-(dx2,dy2) zeichnen
boolean drawImage(Image, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgColor, ImageObserver)
  wie oben, ungeladene Teile mit bgcolor füllen

Beispiel: Das folgende kurze Applet lädt ein Bild namens "image.gif" als Resource (aus einem .jar-File oder aus dem gleichen Verzeichnis wie den Applet-Code). Während des Ladevorgangs wird das Bild in Originalgröße in die obere linke Ecke des Applet-Bereichs gemalt. Sobald es fertig geladen ist, wird es immer der Größe des Applet-Bereichs angepaßt -- also auch beim Ändern der Größe des Appletviewers:

import java.awt.*; import java.applet.Applet; import java.net.*; public class imgtest extends Applet { Image myImage; boolean isReady; public void init() { URL imgURL=getClass().getResource("image.gif"); myImage=getImage(imgURL); isReady=false; } public void paint(Graphics gc) { if (myImage!=null) if (!isReady) { if (isReady=gc.drawImage(myImage,0,0,this)) repaint(); } else { Dimension d=getSize(); gc.drawImage(myImage,0,0,d.width,d.height,this); } } }
Memberfunktionen von Image
int getWidth(ImageObserver)
  Breite in Pixeln (-1, solange unbekannt)
int getHeight(ImageObserver)
  Höhe in Pixeln (-1, solange unbekannt)
ImageProducer getSource()
  Urheber des Bildes
Graphics getGraphics()
  GC für Hintergrund-Bilder
Object getProperty(String,ImageObserver)
  Property lesen
Image getScaledInstance(int height, int width, int hints)
  skalierte Kopie anlegen (s.u.)
void flush()
  alle benötigten Ressourcen freigeben

Wenn das Bild nicht nur gestreckt/gestaucht gezeichnet, sondern auch so abgespeichert werden soll, kann man mit getScaledInstance eine verzerrte Kopie anlegen. Man kann als letzten Parameter angeben, wie die Größenanpassung vor sich gehen soll:

Image-Skalierungsverfahren
SCALE_DEFAULT voreingestellt (z.B. im Appletviewer)
SCALE_AREA_AVERAGING Durchschnitt über Pixel-Cluster verwenden
SCALE_SMOOTH das Verfahren mit der höchsten Qualität
SCALE_FAST das schnellste Verfahren
SCALE_REPLICATE Zeilen/Spalten werden einfach wiederholt

30.5.1 Animationen und Double Buffering

Wenn man umfangreiche Grafik-Operationen durchführt und regelmäßig die Bildschirmdarstellung anpassen muß (z.B. bei Animationen), sollte man mit der Double-Buffering-Technik arbeiten. Die eigentlichen Operationen finden dann in einem unsichtbaren Speicherbereich "im Hintergrund" statt. Erst wenn sie abgeschlossen sind, wird der Speicherbereich in das sichtbare Applet kopiert.

Beispiel: Zunächst schreiben wir ein Applet, das mit einem Thread arbeitet, um eine Animation darzustellen. Unsere Klasse erbt also von Applet und implementiert das Interface Runnable. In der Methode run warten wir einige Millisekunden, berechnen dann das nächste Bild und rufen repaint auf (was letzlich paint aufrufen wird).

Wir stellen einen kleinen blauen Ball (ein einfarbiger Kreis) dar, der im Applet herumfliegt und an den Rändern abprallt. Die Berechnungen für den Abprall-Vorgang sind dabei stark vereinfacht worden, da sie hier nicht im Mittelpunkt stehen. Damit der Hintergrund nicht langweilig weiß bleibt, zeichnen wir dorthin jeweils zwei große farbige Rechtecke.

public final class ball1 extends Applet implements Runnable { private Dimension d; private Random r; private Thread t; private int bx,by,dx,dy; private static final int br=15,bd=2*br+1; public void init() { r=new Random(); d=size(); // deprecated, aber wg. Browser... bx=(int)((d.width-2*br)*r.nextDouble()); by=(int)((d.height-2*br)*r.nextDouble()); dx=(int)(8.0*(r.nextDouble()-0.5)); dy=(int)(8.0*(r.nextDouble()-0.5)); } private void moveBall() { if (bx+bd>=d.width||by+bd>=d.height) { bx=(d.width-bd)/2; by=(d.height-bd)/2; } bx+=dx; by+=dy; if (bx<0||bx>=d.width-bd) bx+=2*(dx=-dx); if (by<0||by>=d.height-bd) by+=2*(dy=-dy); } public void run() { for (;;) { try { t.sleep(30); } catch (Exception e) {} d=size(); moveBall(); repaint(); } } public void start() { if (t==null) { t=new Thread(this); t.start(); } } public void stop() { if (t!=null && t.isAlive()) { t.stop(); t=null; } } public void paint(Graphics gc) { gc.setColor(Color.red); gc.fillRect(d.width/8,d.height/8,(3*d.width)/4,(3*d.height)/4); gc.setColor(Color.green); gc.fillRect(d.width/4,d.height/4,d.width/2,d.height/2); gc.setColor(Color.blue); gc.fillOval(bx,by,bd,bd); } }
Je nach Viewer oder Browser flimmert unser Applet mehr oder weniger stark. repaint löscht zunächst die Grafik, danach folgen unsere eigenen Operationen. Je nach Position des Elektronenstrahls des Bildschirms während unseres Zeichnens sind halbfertige Bilder zu sehen, ein teilweise gelöschtes Bild, ein gerade halbfertiger Kreis für unseren Ball, etc.

Beispiel: Wir verbessern unser Applet, indem wir mit 3 nicht direkt sichtbaren Bereichen arbeiten:

Wenn sich die Größe des Applets ändert, passen wir den ersten und dritten Bereich der neuen Dimension an. Die Hintergrundgrafik wird dann auch neu gemalt.

Ein Test-HTML-Dokument ist folgendes:
<HTML> <BODY BGCOLOR=black> <APPLET CODE=doublbuf.class WIDTH=256 HEIGHT=256> <PARAM NAME=numballs VALUE=42> <PARAM NAME=ballsize VALUE=20> <PARAM NAME=rate VALUE=30> <PARAM NAME=speed VALUE=8> <PARAM NAME=planes VALUE=1> </APPLET> </BODY> </HTML>
Rechts ist nur ein Standbild des Applets gezeigt.

Applet starten

Der Quelltext unseres Applets folgt.

class Wallpaper { private Image img; private Applet a; public Wallpaper(Applet a) { this.a=a; remake(); } public void remake() { Dimension d=a.getSize(); if (img!=null) img.flush(); img=a.createImage(d.width,d.height); Graphics bgGC=img.getGraphics(); for (int i=0,ii=0;i<d.height;++i,ii+=192) { int icol=255-ii/d.height; bgGC.setColor(new Color(0,icol/2,icol)); bgGC.drawLine(0,i,d.width-1,i); } } public void draw(Graphics g) { g.drawImage(img,0,0,a); } } class Ball { private static int count=0; private static Ball[] brs; private static Random rnd; private static Applet app; private static Dimension d; private static int br,bd,nb,speed,planes; private static final int colcode[]={ 1,4,16,5,17,20,21,2,8,32,6,9, 10,18,33,34,24,36,40,22,25,37,26,38,41,42 }; private Image img; private double bx,by,dx,dy; private int bz; public static void init(Ball[] b, int r, int d, int s, int p, Applet a) { brs=b; br=r; bd=d; speed=s; nb=brs.length; planes=p; app=a; rnd=new Random(); } public static void setDim(Dimension d) { Ball.d=d; } private static double norm2(double x, double y) { return x*x+y*y; } public Ball() { makeGfx((count<26)?colcode[count]:(1+(Math.abs(rnd.nextInt())%63))); set(++count-2); } private void makeGfx(int set) { int pixels[]=new int[bd*bd],index=0; int mx=(3*bd)/4,my=bd/4; double br2=br*br; for (int i=0;i<bd;++i) { int y=i-br,d=my-i; d*=d; int l=(int)(Math.sqrt(br2-(double)(y*y))-.5); index+=br-l; for (int j=br-l,e=mx-j;j<=br+l;++j,--e) { double f=0.94*Math.sqrt(d+e*e)/bd-1.0; int comp=95+(int)(160.0*f*f), col=0xff000000; for (int k=3,shift=16,s=set;k>0;--k,shift-=8,s>>=2) if ((s&3)!=0) col|=((comp/(s&3))<<shift); pixels[index++]=col; } index+=br-l; } img=app.createImage(new MemoryImageSource(bd,bd,pixels,0,bd)); } public void draw(Graphics gc) { gc.drawImage(img,(int)bx,(int)by,app); } private boolean overlap(Ball b2) { return (bz==b2.bz) && (norm2(bx-b2.bx,by-b2.by)<=4.0*br*br); } private void set(int mbrs) { int j; do { bx=(d.width-2*br)*rnd.nextDouble(); by=(d.height-2*br)*rnd.nextDouble(); bz=(int)(planes*rnd.nextDouble()); for (j=0;j<=mbrs;++j) if (brs[j]!=this && overlap(brs[j])) break; } while (j<=mbrs); dx=speed*(rnd.nextDouble()-0.5); dy=speed*(rnd.nextDouble()-0.5); } private void frwd() { bx+=dx; by+=dy; } private void bkwd() { bx-=dx; by-=dy; } private void wall() { frwd(); if (bx<0 || bx>=d.width-bd) bx+=2*(dx=-dx); if (by<0 || by>=d.height-bd) by+=2*(dy=-dy); } public static void moveAll() { for (int i=0;i<nb;++i) brs[i].wall(); for (int i=0;i<nb;++i) for (int j=0;j<i;++j) if (brs[i].overlap(brs[j])) { brs[i].bkwd(); brs[j].bkwd(); double nx=brs[i].bx-brs[j].bx, ny=brs[i].by-brs[j].by; double nn2x=-2.0/(nx*nx+ny*ny); double n1=Math.sqrt(norm2(brs[i].dx,brs[i].dy)); double n2=Math.sqrt(norm2(brs[j].dx,brs[j].dy)); double f=(brs[i].dx*nx+brs[i].dy*ny)*nn2x; double g=0.5; if (n1!=0.0) g+=0.5*n2/n1; brs[i].dx=g*(brs[i].dx+f*nx); brs[i].dy=g*(brs[i].dy+f*ny); f=(brs[j].dx*nx+brs[j].dy*ny)*nn2x; g=0.5; if (n2!=0.0) g+=0.5*n1/n2; brs[j].dx=g*(brs[j].dx+f*nx); brs[j].dy=g*(brs[j].dy+f*ny); brs[i].frwd(); brs[j].frwd(); } } public void check() { if (bx>=(d.width-bd)||by>=(d.height-bd)) set(nb-1); } } public final class balls3 extends Applet implements Runnable { private Image bufImage; private Graphics bufGC; private Dimension d; private Wallpaper wpaper; private Thread t; private int nb,rate,speed; private Ball[] balls; private int parsePar(String s, int defVal) { try { return Integer.parseInt(getParameter(s),10); } catch(Exception e) { return defVal; } } public void init() { int speed,rad,diam,planes; d=getSize(); // deprecated, aber wg. Browser... nb=parsePar("numballs",7); rate=parsePar("rate",50); speed=parsePar("speed",8); planes=parsePar("planes",1); diam=parsePar("ballsize",(d.width+d.height)/16); rad=diam/2; diam=2*rad+1; Ball.setDim(d); Ball.init(balls=new Ball[nb],rad,diam,speed,planes,this); for (int i=0;i<nb;++i) balls[i]=new Ball(); wpaper=new Wallpaper(this); setBackground(Color.black); } public void run() { for (;;) { try { t.sleep(rate); } catch (Exception e) {} Ball.moveAll(); repaint(); } } public void start() { if (t==null) { t=new Thread(this); t.start(); } } public void stop() { if (t!=null && t.isAlive()) { t.stop(); t=null; } } public void update(Graphics gc) { d=getSize(); if (bufImage==null||bufImage.getWidth(this)!=d.width ||bufImage.getHeight(this)!=d.height) { bufImage=createImage(d.width,d.height); bufGC=bufImage.getGraphics(); wpaper.remake(); Ball.setDim(d); for (int i=0;i<nb;++i) balls[i].check(); } wpaper.draw(bufGC); for (int i=0;i<nb;++i) balls[i].draw(bufGC); gc.drawImage(bufImage,0,0,this); } }
 
30.4: Font Startseite 31: Das AWT 
 
© 1998 Axel Rogat