Multi-layer (?) bufferedImage

Hi,

ich erzeuge ein bufferedImage und lass dort drauf eine animation ablaufen (mit clearRect, etc.). nun möchte ich die ganze geschichte auf einem anderen hintergrungbild (statisches Image, jpg oder gif) ablaufen lassen, ohne dass es „zerstört“ wird.

danke & gruss
christian schindler

Hi,

ich erzeuge ein bufferedImage und lass dort drauf eine
animation ablaufen (mit clearRect, etc.). nun möchte ich die
ganze geschichte auf einem anderen hintergrungbild (statisches
Image, jpg oder gif) ablaufen lassen, ohne dass es „zerstört“
wird.

Ein paar Worte zu java und Animationen:

Tus nicht.

aber wenn du unbedingt willst:
erzeug alle Bilder im Vorraus (jedes einzelene als BufferedImage).

zeiche in die Bilder was auch immer in die Bilder soll (einschliesslich Hintergrund (evetl. BufferedImage mit 4Byte-non-Alpha)).

stoff alle Bilder in einen Mediatracker und lass loslaufen.

wenn der fertig ist kann java die Bilder mit etwa 20-25 fps auf einem PII 400 darstellen. (Timing über Thread ausserhalb des Event-systems vorrausgesetzt).

viel Glück.

Hi,

danke für deinen Antwort. Die Animation ist momentan kaum zeitkritisch (wenig aufwendig, threads, ausserdem ist der rechner auf dem es laufen soll etwas schneller). ich kanns mir also erlauben. wie unterlege ich denn nun ein anderes bild? ein link auf einen quelltext wäre auch schon nicht schlecht.

gruss
christian

[Bei dieser Antwort wurde das Vollzitat nachträglich automatisiert entfernt]

danke für deinen Antwort. Die Animation ist momentan kaum
zeitkritisch (wenig aufwendig, threads, ausserdem ist der
rechner auf dem es laufen soll etwas schneller). ich kanns mir
also erlauben. wie unterlege ich denn nun ein anderes bild?
ein link auf einen quelltext wäre auch schon nicht schlecht.

ich nehme an das du in jedem Frame 2 Bilder (1x Hintergrund, 1xVordergrund) und irgendwelche Primitive (linien, Kreise, Rechtecken) darstellen willst (für mehrere Bilder in eine for-schleife packen, MediaTracker…)

(Ich hab keine java-doc zur Hand, die Methoden heisen so, oder so ähnlich)

Com ist das Teil das später die Bilder darstellen soll, ich empfehle es von JPanel erben zu lassen.

Das hier in eine init.- Methode stetzen (ausführen BEVOR das Bild zu sehen ist. BUFFER soll eine globale Variable sein, die von Com.paintComponent(Graphics g) (paint()) aus zu erreichen ist.

public void init (BufferedImage BUFFER, Component Com){
Image Hintergrund = Toolkit.getDefaultToolkit().loadImage (HintergrundFile);
Image Vorne = Toolkit.getDefaultToolkit().loadImage (VorneFile);

BUFFER = new BufferedImage (sizeX,SizeY,BufferedImage.TYPE_4BYTE); //das Type ist nur geraten.
Graphics g = BUFFER.getGraphics(); //alternativ Graphics2D g2 = (Graphics2D) BUFFER.getGraphics();
g.drawImage (PosX,PosY,Hintergrund,Com); //hier ist Com WICHTIG.
g.drawImage (PosX2,PosY2,Vorne,Com); //Com WICHTIG.

g.setColor (Color.Green);
g.drawLine (x1,y1,x2,y2); //andere Primitive siehe javadoc.

MediaTracker ME = new MediaTracker(Com);
ME.add(BUFFER); //kann auch ME.add(BUFFER,1); sein…
try {
ME.waitForAll();
} catch (Exception E){
E.printStackTrace(); irgendetwas an den Bildern ist faul… Datei fehlt, Format unbekannt…
}
}

durch ME.waitForAll() wird das Bild gerendert, d.h. für die Darstellung auf Com vorbereitet. In Echtzeit kann java das nicht (nicht mit grossen Bildern).

Com must du von java.awt.Component (alternativ geht auch javax.swing.JPanel) erben lassen, und dann public void paint(Graphics g) (public void paintComponent(Graphics g) für javax.swing.JPanel) überladen:

public void paint (Graphics g){
g.drawImage (posX,posY,BUFFER,this);
}

BUFFER durch den Timer-Thread regelmässig änderen danach Com.repaint() aufrufen.

fertig.

Du must aufpassen das Vorne ein Bild mit Transparenz ist. wenn das Bild keine transparenz hat wird java es einfach über den Hiintergrund malen. Am besten klappt die Transparenz bei .gif .
.jpg, .png, .tif .bmp kanns du alle vergessen. dynamisch die Transparenz auf eine Farbe zu legen ist theo. möglich, lässt aber auch Athlon XP´s zusammenbrechen.

Ausserdem ist es gut das Sun-SDK 1.4.0 zu installieren, das beschleunigt die Sache um denFaktor 5 gegenüber Sun-SDK 1.3.0 und um den Faktor 35 gegenüber der aktuellen JVM von M$. Die BlackDown-JVM soll auch mit der 1.4.0 in dem Punkt imhalten können (für linux).

viel Glück.

1 Like

da sog i merci (o.T.)

[Bei dieser Antwort wurde das Vollzitat nachträglich automatisiert entfernt]

Hi,

einen schoenheitsfehler hat die geschichte allerdings schon: wenn ein fenster mein Panel verdeckt bzw. das Fenster minimiert wird, ist das zweite Objekt (die Primitives: ich mal tatsaechlich Linien ueber einen Hintergrund) weg.

Ich erinner mich, ich habe damals bewusst BufferedImage genommen, damit die Zeichnung gespeichert bleibt. Dadurch dass ich jetzt zwei schichten uebereinanderlege, bleibt nur die hintergrund-schicht erhalten.

Idee?

Danke
Christian

[Bei dieser Antwort wurde das Vollzitat nachträglich automatisiert entfernt]

Hi,

einen schoenheitsfehler hat die geschichte allerdings schon:
wenn ein fenster mein Panel verdeckt bzw. das Fenster
minimiert wird, ist das zweite Objekt (die Primitives: ich mal
tatsaechlich Linien ueber einen Hintergrund) weg.

Ich erinner mich, ich habe damals bewusst BufferedImage
genommen, damit die Zeichnung gespeichert bleibt. Dadurch dass
ich jetzt zwei schichten uebereinanderlege, bleibt nur die
hintergrund-schicht erhalten.

bist du sicher dass:

  1. der Mediatracker das Ding mit Linien geschluckt hat ?
  2. es der richtige GraphicsContext/Buffer war?
  3. das BufferedImage in der richtigen Methode (swing: paintComponent(), awt paint()) an den eingentlichen Context geschickt wird (per draw)?
  4. änderes du die Farbe der Linien irgendwann auf transparent ?

wenn das nicht war poste mal quellcode.

bist du sicher dass:

  1. der Mediatracker das Ding mit Linien geschluckt hat ?
  2. es der richtige GraphicsContext/Buffer war?
  3. das BufferedImage in der richtigen Methode (swing:
    paintComponent(), awt paint()) an den eingentlichen Context
    geschickt wird (per draw)?
  4. änderes du die Farbe der Linien irgendwann auf transparent
    ?

wenn das nicht war poste mal quellcode.

Hi,
anbei der Quellcode der Klasse PLAYFIELD, darauf will ich zeichnen. Wichtig ist vorallem die Methode updateCoords. Meine anderen Klassen bedienen sich der Funktion um die neuen Koordinaten zu malen. setPunkt setzt nur an gewissen Koordinaten einen etwas dickeren „Knödel“ hin. (zoomIn und zooOut sind momentan auch nicht so wichtig. Leider funktionieren die Funktionen eh nur auf den oberen linken Bildschirmteil.)

Ich habe noch relativ wenig Erfahrung in der AWT/Swing-Programmierung (drum funktioniert vieles nur nach try&error), und mir fehlt noch etwas das Grundverständnis. Ich lasse mich also gerne Denkfehler, Optimierungsmöglichkeiten, etc. aufklären.

Danke & Gruss
Christian

import java.awt.\*;
import java.awt.image.\*; // BufferedImage
import java.awt.geom.\*;
import javax.swing.\*;

import java.io.\*;
import java.net.\*;


public class Playfield extends JPanel {
 private Image hintergrund /\*, puffer \*/ ;

 private BufferedImage bi;

 private int scr\_width, scr\_height;
 private double zoomfactor = 1;

 public Playfield(int scr\_width, int scr\_height) {
 hintergrund = Toolkit.getDefaultToolkit().getImage("gfx/map.gif");

 this.scr\_width = scr\_width;
 this.scr\_height = scr\_height;
 bi = new BufferedImage(scr\_width, scr\_height, BufferedImage.TYPE\_INT\_RGB);
 Graphics2D g2 = bi.createGraphics();
 // clear
 g2.setBackground(Color.lightGray);
 g2.clearRect(0, 0, scr\_width, scr\_height);

 MediaTracker tracker = new MediaTracker(this);
 tracker.addImage(hintergrund, 0);
 tracker.addImage(bi, 0);
 try {
 tracker.waitForAll();
 } catch (InterruptedException ign) { }



 }

 /\*\*
 \* clears the panel (User action)
 \*/
 public void clearPlayfield() {
 Graphics2D g2 = (Graphics2D)bi.getGraphics();
 g2.clipRect(0, 0, scr\_width, scr\_height);
 g2.setColor(Color.lightGray);
 Rectangle2D.Double rect = new Rectangle2D.Double(0,0,scr\_width,scr\_height);
 g2.fill(rect);
 g2 = (Graphics2D)this.getGraphics();
 g2.clipRect(0, 0, scr\_width, scr\_height);

 g2.drawImage(bi, 0, 0, null);
 g2.drawImage(hintergrund, 0, 0, this);
 }

/\*- Zoom -------------------------------------------------------------------\*/
 public void zoomIn() {
 System.out.print("Zoom in: " + this.zoomfactor + " -\> ");
 if(this.zoomfactor == 0) this.zoomfactor += 0.1;
 else if(this.zoomfactor ");
 if(this.zoomfactor == 0) return;
 else if(this.zoomfactor \> 0 && this.zoomfactor x\_alt+30||yy\_alt+30) { ; }
 else {
 Graphics2D g2 = (Graphics2D)bi.getGraphics();
 g2.clipRect(x\_alt-2, y\_alt-2, 4, 4);
 g2.setColor(farbe);

 g2.drawLine(x\_alt, y\_alt, x, y);

 g2 = (Graphics2D)this.getGraphics();
 g2.clipRect(x\_alt-2, y\_alt-2, 4, 4);

 g2.drawImage(hintergrund, 0, 0, this);
 g2.drawImage(bi, 0, 0, this); 
 }
 }


 public void setPunkt(int x, int y, int r, Color pkt\_farbe) {
 Graphics2D g2 = (Graphics2D)bi.getGraphics();
 g2.setColor(Color.black);
 g2.drawOval(x, y, r+1, r+1);
 g2.setColor(pkt\_farbe);
 g2.fillOval(x, y, r, r);
 }


 public void update(Graphics2D g2) {
 paint(g2);
 }


 public void paint(Graphics g) {
 g.drawImage(bi, 0, 0, null);
 g.drawImage(hintergrund, 0, 0, null);
 }


 public int getScreenHeight() { return scr\_height; }
 public int getScreenWidth() { return scr\_width; }
}

Hoffe ich war nicht zu hart oder dreb, hatte wenig Zeit und bins nicht gewöhnt anderer Leute Code umzubauen. (hab zoom ganz ausgelassen, der Fehler war ganz woanders)

import java.awt.\*;
import java.awt.image.\*;// BufferedImage
import java.awt.geom.\*;
import javax.swing.\*;

//.io ?
import java.io.\*;

//wozu brauchts du HIER .net ?
import java.net.\*;

public class Playfield extends JPanel {

 //komische Nameswahl, du benutzt hintergrund als Vordergrund (im Verhältins zu bi).
 private Image hintergrund /\*, puffer \*/ ;
 private BufferedImage bi;

 //braucht man nicht, hat JPanel schon von sich aus. (es sei den du willst nur einen Teil des JPanel selbst benutzten...)
 private int scr\_width, scr\_height;

 public Playfield(int scr\_width, int scr\_height) {
 hintergrund = Toolkit.getDefaultToolkit().getImage("gfx/map.gif");

 //arbeite mit this.setHight (scr\_height); Dann weis das JPanel auch das es gerade grösser geworden ist.
 this.scr\_height = scr\_height;
 //idem.
 this.scr\_width = scr\_width;

 bi = new BufferedImage(scr\_width, scr\_height, BufferedImage.TYPE\_INT\_RGB);
 Graphics2D g2 = bi.createGraphics();
 // clear
 g2.setBackground(Color.lightGray);
 g2.clearRect(0, 0, scr\_width, scr\_height);
 MediaTracker tracker = new MediaTracker(this);
 tracker.addImage(hintergrund, 0);
 tracker.addImage(bi, 0);
 try {
 tracker.waitForAll();
 } catch (InterruptedException ign) { 
 //Es ist immer schlecht so eine Stelle leer zu lassen wenn man debugged also:
 System.out.println ("OPS :"+ign.toString());
 }

 }
/\*\*
\* clears the panel (User action)
\*/
 public void clearPlayfield() {
 Graphics2D g2 = (Graphics2D)bi.getGraphics();
 //wieder die bemerkung mit getHeight()
 g2.clipRect(0, 0, scr\_width, scr\_height);
 g2.setColor(Color.lightGray);
 Rectangle2D.Double rect = new Rectangle2D.Double(0,0,scr\_width,scr\_height);
 g2.fill(rect);

 //nein ! NIE direkt auf den Graphics-Context zugreifen, immer mit repaint() arbeiten.
 //java macht das genau umgedreht zu C. alles was malten betrifft in paint(g) verbauen.
 // alles raus bis
 g2 = (Graphics2D)this.getGraphics();
 g2.clipRect(0, 0, scr\_width, scr\_height);
 g2.drawImage(bi, 0, 0, null);
 g2.drawImage(hintergrund, 0, 0, this);
 // hier. und durch 
 repaint();
 //ersetzen.
 //oder mit paintImmediately() arbeiten, aber nur wenns SEHR schnell sein MUSS.
 }
 public void updateCoords(int x\_alt, int y\_alt, int x, int y, Color farbe) {
 //das geht einfacherer:
 /\*
 if(x == 0||y ==0||x\_alt == 0||y\_alt ==0||xx\_alt+30||yy\_alt+30) { 
 ; 
 }else {
 \*/ //so:
 if(!(x == 0||y ==0||x\_alt == 0||y\_alt ==0||xx\_alt+30||yy\_alt+30)) { 
 Graphics2D g2 = (Graphics2D)bi.getGraphics();
 g2.clipRect(x\_alt-2, y\_alt-2, 4, 4);
 g2.setColor(farbe);
 g2.drawLine(x\_alt, y\_alt, x, y);


 //nein ! NIE direkt auf den Graphics-Context zugreifen, immer mit repaint() arbeiten.
 // alles raus bis
 g2 = (Graphics2D)this.getGraphics();
 g2.clipRect(x\_alt-2, y\_alt-2, 4, 4);
 g2.drawImage(hintergrund, 0, 0, this);
 g2.drawImage(bi, 0, 0, this);
 // hier. und durch 
 repaint();
 //ersetzen.
 }
 }

 public void setPunkt(int x, int y, int r, Color pkt\_farbe) {

 //Du weist das der 1. Punkt dann stehen bleibt, sprich der Bildschrim irgendwann voll ist ?
 Graphics2D g2 = (Graphics2D)bi.getGraphics();
 g2.setColor(Color.black);
 g2.drawOval(x, y, r+1, r+1);
 g2.setColor(pkt\_farbe);
 g2.fillOval(x, y, r, r);
 }

 // muss man nicht unbedingt überschreiben, das double-buffering ausschalten reicht eigentlich.
 public void update(Graphics2D g2) {
 paint(g2);
 }


 public void paint(Graphics g) {
 //FALSCH ! das letzte Argument muss this, nicht null sein:
 //das muss aber nicht der Fehler sein....
 g.drawImage(bi, 0, 0, null);
 //idem.
 g.drawImage(hintergrund, 0, 0, null);
 }

 //besser du rufts super.getWidth() auf
 public int getScreenWidth() { return scr\_width; }
 //siehe oben.
 public int getScreenHeight() { return scr\_height; }
}

Meiner Meinung nach ist das Problem mit „Linien weg nach resize/max.“ relativ einfach zu erklären:
Deine paint()-Methode malt das „HintergrundBild“ zuletzt, und übermalt dabei „bi“. Die Linien die über getGraphics() gemalt wurden sind weg nach einem resize weg (java-logik => never-clean), die Linien von bi hast du nie gesehen (du malts sie ja 2x, einmal bi einmal getGraphics()). Generel war bi ziemlich sinnfrei, es lag unter den hintergrund. Überprüf die Transparenz von „map.gif“, stell sicher das java das auch als transparent ansieht und versuchs nochmal.

Oder machs so:

public void init (){
 hintergrund = Toolkit.getImage ("...");
 MediaTracker ... usw
}

public paint(Graphics G){
 g.drawImage (hintergrund,0,0,this);
 for (int i=0;i
wobei linien ein int[][] ist das du in setPunkt(...) veränderts.

wenn du die doublebuffer-funktion von JPanel nur einschaltets müsste das sogar bei vielen Linien flüssig laufen (bei 100 Linien etwa 20-25 Bilder/s auf PII 450 unter JDK.1.3.0). ausgeschaltet könnte man das aufbauen der Linien u.U. bei vielen Linien sehen.

Erst wenn 2 Images gleichzeitig auftauchen sollen, und sich bewegen ist custom-doppel-buffering sinnvoll.


kleine Kurzeinführung in java und GUI: (ich lass alles weg was nicht direkt mit der GUI zu tun hat und setzt das Thread-konzept vorraus)
java startet bei GUI-Applicationen immer 2 Threads, einen mit main(String[] A) und einen für die interaktion mit dem User. Dieser 2. Thread wird "event" oder "paint"-thread genannt und ruft paint(), update(), und alle Actionlistener die auf Klicks, MouseXYZ, Buttons, TextFelder und ähnliches (was der User sieht) auf. Dieser Thread passt auf NICHTS auf und sollte nicht blockiert werden. Ist er blockiert so passiert keine Interaktion mehr mit dem User, die GUI steht komplett (nichtmal coursor-blinken geht). Diesem Thread sollte man nicht in die Quere kommen.

Der Thread unterwirft sich nur dem RepaintManager. Er wird "aktiviert" durch repaint(), paintImmediately() und invokeLater(runnable r) (und ähnliches).

wenn man aber getGraphics benutzt bekommt man einen GraphicsContext (Graphics ist ein Teil davon) der von den event-thread ebenfalls benutzt wird. Da wäre nicht schlimm wenn Graphics "synchronized" wäre, ist es aber nicht. Also entweder man stellt den Thread über den RepaintManager ruhig (schlecht), oder man synchronisiert alle Zugriffe auf Graphics-Objecte (schlimmer, da syn. immer langsam ist).

ergo gehört jeder Code der auf Graphics (einer sichtbaren Componente) zugreift in paint(Graphics g) (paintChildren(),...). Und diese Methode ruft man \_nicht\_ direkt auf, ausserdem sollte diese Methode "schnell" sein. Will man ein "paint()"-aufruf durch den event-thread "provozieren" so ruft man repaint() auf.


cu
1 Like