performance problem: paintcomponent, alphacomp, bufferedImag

  • Themenstarter Themenstarter Danger
  • Beginndatum Beginndatum
Status
Nicht offen für weitere Antworten.
D

Danger

Gast
Hallo,

ich habe ein Performance Problem. In meiner Klasse ImageLabel (ein einfaches JLabel, welches ein BufferedImage als Hintergrundbild aufnimmt) gibt es üble Rechenprobleme. Ich habe einen JSlider, mit dem ich das Hintergrundbild transparent machen kann. Leider kommt es bei Betätigen des Sliders zu argen Verzögerungen (je größer width und height von ImageLabel ist, umso mehr ruckelt das ganze beim verschieben). Hier mal die relevante Klasse ImageLabel.

Wer kann mir hierzu helfen. Wie kriege ich das in den Griff :;-(

Code:
import java.awt.AlphaComposite;
import java.awt.Composite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;

import javax.swing.JLabel;

public class BgImageLabel extends JLabel {

  private MainPanel layer;
  private float transFactor;
  private Image bgImage;  
  private int width, height;
 
  public BgImageLabel(MainPanel layer) {

    super();    
    this.layer = layer;
    this.width = layer.getLayerWidth();
    this.height= layer.getLayerHeight();
    setSize(width, height);
    transFactor = 1.0f; 
   
  }

  public void setBgImage(Image image) {
    this.bgImage = image;
  }

  protected void paintComponent(Graphics g) {    
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D)g;    
//    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, transFactor));
    g2.drawImage(bgImage,0,0,width,height, this);
    
  }

  
  public void updateTransparency(float factor) {
    this.transFactor = factor;
    updateUI();
  }
}
 
also grundsätzlich ist das ein problem bei java, daß operationen auf bilder verglichen mit C++ eine halbe ewigkeit dauern, zumindest bei Java2D. bei JAI wird dieses problem umgangen, indem die operation in optimierter form nur auf die bildteile durchgeführt wird, die gerade sichtbar sind bzw. gebraucht werden.

wenn du nicht auf JAI umsteigen willst, gibts aber schon noch einige optimierungen:

1. paintComponent(Graphics). es ist ok, daß du diese methode verwendest, genau so kannst du aber auch gleich in der hierarchisch darüberliegende methode paint(Graphics) das zeichnen des bildes erledigen lassen. den aufruf zur super-methode kannst du weglassen, denn diese zeichnet ja zuerst das normale JLabel, welches du dann mit dem bild übermalst (der super-methode-aufruf ist also sinnlos und verbraucht unnötig zeit).

2. rufe anstatt updateUI() einfach nur repaint() auf. updateUI() führt dazu, daß die gesamten variablen, die das jeweilige LabelUI bereitstellt neu vom UIManager abgeholt werden. abgeschlossen wird dann diese aktion durch ein neu zeichnen des JLabels. letzteres kannst du per repaint() aber auch gleich anstoßen. die UI-variablen hast du ja nicht geändert. somit sparst du auch hier zeit.

3. um unnötiges zeichnen zu unterbinden würde ich mit einer boolschen variablen arbeiten (z.b. boolean hasChanged). diese solltest du meiner meinung nach in deiner methode updateTransparency(float) auf true setzen um sich nach erfolgreichen durchlaufens der paint(Graphics) methode wieder auf false zurück zu setzen. am anfang von updateTransparency(float) würde ich, falls hasChanged == true abbrechen. der jsilder liefert nämlich viel häufiger werte, also java dein bild neu zeichnen kann. vor allem, wenn das bild größer ist.

4. durch abschalten vieler qualitativ aufwertender funktionen der RenderingHints kannst du das ganze etwas beschleunigen (ich schätze bis zu 50%). kommt hier aber auch wieder auf die größe des bildes drauf an und darauf, auf was du an zeichenqualität verzichten kannst. aber da die RenderingHints ja ohnehin sich in deiner import-liste befinden und im sonstigen geposteten programmcode nicht, geh ich davon aus, daß du schon damit rumgespielt haben wirst.

5. anstatt von JLabel kannst du auch von JPanel oder gleich von JComponent erben. irgendwelche JLabel-spezifischen operationen führst du auf deine component eh nicht durch. also warum den ganzen ungenutzen müll mitschleppen.

6. das ultra-tuning wird deine programm warscheinlich erst erleben, wenn du das color-model des bildes änderst. hätte hier den vorschlag ein IndexedColorModel zu verwenden. anstatt das du per Composite die transparenz des Graphics-objektes änderst, könntest du über dieses ColorModel einfach nur die transparenz der verwendeten farben ändern. wenn dein bild aber ein reales foto ist, wird das schwierig. theoretisch müsstest du zur umwandlung in ein IndexedColorModel die verwendeten farben ermitteln und daraus dann ein neues bild erzeugen.

7. wozu machst du das bild überhaupt transparent. wenn du das bild beispielsweise unten zeichnest und darüber eine geschlossene, gleich große, halbtransparente oberfläche, kann dir niemand sagen, ob nun das bild durchscheinend geworden ist und darunter die oberfläche liegt oder ob die oberfläche durchscheinend geworden ist und das bild unten liegt. was ich meine ist, ob nicht vielleicht andersherum zu zeichnen schneller geht.

8. weiß net, obs performance-technisch was bringt. ich würde beim zeichnen des bildes drei dinge anders machen. erstens würde ich gar nicht auf Graphics2D casten, da Graphics bereits drawImage mitbringt. zweitens würde ich eine andere drawImage-methode verwenden und zwar die hier: drawImage(Image,int,int,ImageObserver), da die von dir verwendete erst mal überprüfen muss, ob das bild nicht schon die breite und höhe hat, die du ihr da als parameter übergibst. ist das bild nämlich ungleich, skaliert diese methode das bild entsprechend. drittens kannst du anstatt this (JLabel->JComponent->ImageObserver) auch null übergeben, denn hier wird kein ImageObserver benötigt. letzterer würde vom aufbau des bildes nachrichten empfangen, welche du aber gar nicht verarbeitest (warum solltest du auch? hab ich in den 6 jahren meiner java-programmiererei noch nie gemacht).

viel glück

wayne
 
ach ja ... bezüglich punkt 8 ist mir das grad eben erst aufgefallen. das ist warscheinlich auch der hauptgrund, warum dein programm so lange braucht. wenn du bei jedem repaint das bild erneut skalierst benötigt es natürlich ewigkeiten um gezeichnet zu werden. besser ist, du erzeugst in deiner methode setBgImage(Image) ein skaliertes abbild deines bildes und zeichnest das dann in der paint(Graphics)-methode. wenn sich die größe deiner component niemals ändert klappt das ganz gut, ansonsten musst du halt auf größenveränderungen achten, beispielsweise in dem du dich als ComponentListener auf deine component einträgst und auf componentResized(ComponentEvent) hörst. dort müsstest du im falle des falles dann das skalierte bild verwerfen und eine neue skalierte version des orginalbildes erzeugen (abschließend wie immer repaint() aufrufen).

wayne
 
Bingo. Hier hat ein Meister gesprochen, äh ich meine geschrieben 😉

noch kurze anmerkungen:

6) fällt flach.
7) fällt auch flach, da alle komponenten über dem hintergrundbild nicht transparent werden können.



alle für mich durchführbaren, trivialen Vorschläge hab ich nun mal geändert und es hat deutlich was gebracht !

warum ich jetzt allerdings paint und nicht paintComponent aufrufen soll, versteh ich nicht ganz. Man liest doch immer wieder hier, dass bei Swing immer paintComponent benutzt wird ???:L

und: warum nicht auf graphics2sd casten, wenn es nur "darin" das gibt, welches ich brauche (auf Graphics kann ich ja kein AlphaComposite setzen) ?

Aber vielen Dank für deine ausführlichen Anmerkungen. War auch mal sehr informativ.

JAI werd ich mir zu einem späteren Zeitpunkt noch anschauen, jetzt muss ich erst mal diese Riesenbaustelle und viel Elemetares in den Griff kriegen.


Code:
import java.awt.AlphaComposite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import javax.swing.JComponent;
import javax.swing.JFrame;


public class BgImageLabel extends JComponent {

  private MainPanel layer;
  private float transFactor;
  private Image bgImage;  
  private int width, height;
  static int count = 0;

  public BgImageLabel(MainPanel layer) {

    super();    
    this.layer = layer;
    this.width = layer.getLayerWidth();
    this.height= layer.getLayerHeight();
    setSize(width, height);
    transFactor = 1.0f; 
    
    
  }

  public void setBgImage(Image image) {    
    this.bgImage = image.getScaledInstance(width,height,0);    
  }

  public void paint(Graphics g) {       
        
    Graphics2D g2 = (Graphics2D)g;
    render(bgImage,g2,transFactor);   
  }
  
  private void render(Image img, Graphics2D g2,float trans){
    AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, trans);
    g2.setComposite(ac);
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
//    g2.drawImage(bgImage, (width-bgImage.getWidth(this))/2,(height-bgImage.getHeight(this))/2 , this);
    g2.drawImage(bgImage, 0,0, null);
    
} 

  
  public void updateTransparency(float factor) {    
    this.transFactor = factor;
    repaint();
    
  }
 
*g*

vielen danke für's kompliment (bezüglich des meisters)

ja, das mit paintComponent ... eigentlich ist es korrekt, wenn man paintComponent aufruft, denn von paint aus werden alle paint-methoden aufgerufen, also auch beispielsweise paintChildren. da du aber in deinem fall deine component nicht als träger weiterer componets verwendest und das bild, welches du zeichnest, sich über die gesamte größe der component erstreckt, machen aufrufe von paintChildren(Graphics), paintBoder(Graphics), etc. eigentlich keinen sinn. und da du ja nach möglichkeiten des tunings gesucht hast ... es spart auch zeit, wenn man sinnlose methoden einfach gar nicht erst aufruft, zumindest war dies mein gedankenansatz. das optische ergebnis wird sich nicht unterscheiden und über irgendein entstehendes problem dadurch, daß du das zeichnen in der methodenhierarchie bereits weiter oben anfängst, bin ich in meiner laufbahn noch nie gestolpert.

das mit Graphics2D und dem casten war ein fehler von mir. hatte übersehen, daß zwar die drawImage-methode auch in Graphics mitgeführt wird, jedoch die setComposite-methode nicht. diesbezüglich hast du also vollkommen recht damit das objekt nach Graphics2D zu casten.

wünsche weiterhin erfolgreiches schaffen

wayne

p.s. bin bezüglich der klasse BufferedImage zwar schon öfter darüber gestolpert, aber jetzt gerade weiß ich's wiedermal nicht mehr auswendig. ich meine mich zu erinnern, daß die methode getScaledInstance(int,int,int) nicht der schnellste weg war eine skalierte version der bildes zu erhalten. soviel ich mich erinnere war einer der schnellsten ein neues BufferedImage mit der zielgröße zu erzeugen (im gleichen farbmodel), sich mittels getGraphics() von dem BufferedImage ein Graphics-objekt geben zu lassen und dann das originalbild mittels drawImage(Image,int,int,int,int,ImageObserver) skaliert einzeichnen zu lassen. das musst du mal ausprobieren und zeitlich abmessen, wenn du diesen schritt auch noch etwas tunen willst.
 
jo, das mit dem bufferedimage statt scaledinstance habe ich hier schon öfter gelesen, aber ich kriege das seit 2 stunden nicht für meinen fall hin :'-(

Code:
public void setBgImage(Image image) {
    
   this.bgImage = image.getScaledInstance(width, height,Image.SCALE_SMOOTH);
    
  }
  

  public void paint(Graphics g) {  
    
    Graphics2D g2 = (Graphics2D)g;   
    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, transFactor));
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);   
    
    g2.drawImage(bgImage, 0,0,null);   
  }


Wie krieg ich das bgImage skaliert ohne diese obige Methode ?


seufz,
 
also hier mal einer von ungefähr 1.392.239 Versuchen:

Code:
  public void setBgImage(Image image) {
    
//   this.bgImage = image.getScaledInstance(width, height,Image.SCALE_SMOOTH);
//    this.bgImage = image;
    //Instanzvariable bgImage (Image)
    bgImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
    thisBufferedImage = image;
    
  }
  

  public void paint(Graphics g) {  
    
    Graphics2D g2 = (Graphics2D)bgImage.getGraphics();   
    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, transFactor));
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);      
    g2.drawImage(thisBufferedImage, 0,0,null);   
  }


:bloed:
 
sorry für diese kurze flut hier. aber nun hat der 1.000.002 Versuch eine funktionierede Lösung gebracht: Aber, kann man dies so lassen, oder ist das Bullshit ?

Code:
  public void setBgImage(Image image) {
    
//   this.bgImage = image.getScaledInstance(width, height,Image.SCALE_SMOOTH);
//    this.bgImage = image;
    bgImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
    thisBufferedImage = image;
    
  }
  

  public void paint(Graphics g) {  
    
    Graphics2D g2 = (Graphics2D)g;   
    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, transFactor));
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
    bgImage.getGraphics().drawImage(thisBufferedImage,0,0,bgImage.getWidth(null),bgImage.getHeight(null),null);
    g2.drawImage(bgImage, 0,0,null);   
    
  }
 
ne, ne ... ist verkehrt, wie du schon richtig bemerkt hast. lass dir zeigen:

Code:
public void setBgImage(Image img){
    // zunächst das originalbild sichern
    thisBufferedImage = (BufferedImage) img; 

    // dann neues bild erzeugen und reinmalen
    bgImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
    Graphics g = bgImage.getGraphics();
    g.drawImage(thisBufferedImage,0,0,width,height,null);
}


public void paint(Graphics g){
    Graphics2D g2 = (Graphics2D) g;    
    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, transFactor)); 
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);      
    g2.drawImage(bgImage, 0,0,null);  
}

so klappt das ganze.

viel glück

wayne
 
jetzt haben sich unsere antworten überschnitten. deine version wäre aber auch nicht das, was ich meinte. versuchs nun mal mit meiner gerade geposteten.

viele grüße

wayne

p.s. muss jetz offline, bin erst morgen wieder da um zu sehen ob du erfolg hattest
 
jou, sieht gut aus. ich danke dir vielmals und hoffe bei gelegenheit auf weiteren fachmännischen rat. :toll:

gruß danger
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben