2

I try to optimize the drawing of my application. Currently i have implemented an animation as well as several GUI components. Some are separated exactly, some overlap each other. Currently i face a problem within the overlapping of swing components. A part of the GUI, which overlaps my animation, needs to draw a lot of Strings and - into an Jlist placed - common swing components.

In result the overlapping GUI becomes redrawn as often as the animation gets an update. I tried using quite a lot of different methods to make sure, whats drawn in front of each other. Things like GlassPane, Jlayeredpane. Unfortunately in any of these tries, the overlapping Menus paintcomponent method, which only need to become called as the user interacts with them, gets called frequently due to animation and causes a quite high cpu usage.

Ive tried to position the Menus in a lower order within the Layeredpane, i.e.:

    getLayeredPane().add(map, JLayeredPane.DEFAULT_LAYER);
    getLayeredPane().add(mapController, JLayeredPane.PALETTE_LAYER);
    getLayeredPane().add(settings, JLayeredPane.PALETTE_LAYER);        
    getLayeredPane().add(painter, JLayeredPane.POPUP_LAYER);   

During the paint process of the painter i tried to modify the area - i.e.:

@Override
protected void paintComponent(Graphics g) {

    g2 = (Graphics2D)g;
    g2.setRenderingHints(DefaultResources.getRenderQuality());

    g2.clip(JMain.getInstance().getMapBounds());

    ...}

Well - as soon as the painter component !isOpague(); All components underneath become redrawn. Unfortunately if i do place the menus in a higher order, they as well need to become redrawn with any animation update.

Does somebody has any idea, how to avoid a permanent redraw of overlapping components with an animated component?

The only solution i have seen was using heavyweight containers. Unfortunately the relative positioning has also shown a behavior during moving purposes, which aren't appropriate.

Thanks for any kind of advice!!

mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • 2
    1. please whats `g2.clip(JMain.getInstance().getMapBounds());`, 2. for better help sooner post an [SSCCE](http://sscce.org/) demonstraded caused issue only – mKorbel Oct 01 '12 at 13:23

3 Answers3

3

Well, its pretty obvious that if you have overlapping non-opaque components all of them will be repainted on any changes in one of them unless you optimize your animation repaint calls to some specific rectangles, so there won't be any useless operations.

Let me describe how Swing works a bit more - all of the paintings you do in paint, paintComponent and other methods (which get called on each component repaint) are done onto subimages of a single image that holds a "cached" version of the whole frame interface.

Now imagine that you change something in your UI (add/remove/repaint component) - that final image (or atleast a small part of it that contains your component) must be properly updated. To do that in case your component is NOT opaque - all of sub-components will be repainted with your component bounds as a repaint rect to create a proper background for your component. If your component is opaque - it will be the only one repainted, but it also has to fill the whole bounds rect on its own, otherwise you will see awful painting artifacts behind your component on each repaint.

To summ up - to avoid pointless repainting of overlapping components there are a few approaches:

  1. Optimize animation repaint calls to areas you actually need to repaint
  2. Use opaque components, unless you want to paint something transparent in your component
  3. Optimize all overlapping components painting operations so repaints won't take much time

There still might be more optimization approaches depending on your specific case, but you will have to find them on your own, since that is impossible without seeing the whole picture.

You can also find a lot of useful information about optimizations in Swing in this book: Filthy Rich Clients

Mikle Garin
  • 10,083
  • 37
  • 59
  • Hey thanks a lot for these answers. I have tried to do exactly, what you do describe in your 1. approach. For instance, i have made an Area object, which has the boundaries of my map. Next i have subtracted the boundaries of the menu. There i have clipped the redrawing as shown in the source code in my first post. Unfortunately, even as the overlapping map does not paint anything within the bounds of my menu, the paintcomponent method of the menu gets called. I have also tried to make that one opague, lay it on top of the map or underneath - it still gets called. Can i prevent that? – user1514195 Oct 02 '12 at 06:05
  • Hm i have just seen, what you meant by the areas you mentioned. Therefore i need to tell the repaint the boundaries, that shall become repainted. For a clip, i am already within the paintcomponent method, which triggers those of the menus. Is there some kind of opportunity to forward shapes rather than rectangles in a repaint call? – user1514195 Oct 02 '12 at 06:30
  • 1
    @user1514195 nop, you can only work with rectangles when repainting something. So you have to choose the repainted area wisely. It might be also useful to call few repaints instead of a single one in case it might exclude some overlaying components from repainting. Still make sure that component that will get multiply repaint calls has optimized painting methods. – Mikle Garin Oct 02 '12 at 09:36
  • Do you think of something special in terms of "optimized painting methods"? Mine is more or less a couple of static drawing Methods which depend on a data model, which changes independent from the painting algorithm. Therefore i do not create any objects or sth. – user1514195 Oct 02 '12 at 09:42
  • @user1514195 "optimized" means "optimized for your specific case". There is no common/simple way to just improve painting performance. There are some general tips and tricks that might help, but they also might decrease performance if used inappropriatly. If you have some data model that should update UI on its changes - collect the changes and apply them alltogether, optimize your repaint calls, remove unnecessary repaint calls, properly clip the painting areas if its needed. These are general tips for any case, actually. – Mikle Garin Oct 02 '12 at 12:42
  • @user1514195 To say anything more specififc i need an example that contains the exact problem with performance/repaints... – Mikle Garin Oct 02 '12 at 12:43
0

Well in terms of optimization i do have a component, which is causing quite some trouble, but that one i have planned to rewrite. Therefore i just want to make sure, that painting areas are properly. Following that point, i have computed all necessary areas and filled them into a list, which i pass as data has changed.

Its working properly as long as i only have one rectangle applied. As soon as i pass a second one, it seems its y - extension becomes ignored. For instance:

[x=0,y=0,width=322,height=20] [x=0,y=620,width=322,height=20]

There everything between y=20 and y=620 also gets redrawn.

for (Rectangle rec : clippingAreas) {                    
                 painter.repaint(rec);
            }

Ok i have tried paintImmediately within the EDT, which does work for now, but i wonder if this is a proper way to go:

SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                for (Rectangle rec : clippingAreas) {
                    painter.paintImmediately(rec);                        
                }
            }
        });
  • It works if i am using myPainter.paintImmediately(rec), but that one does produce quite some painting artifacts. – user1514195 Oct 02 '12 at 13:09
  • And how exactly do you "pass a second one"? Could you show the exact code that repaints both rects? – Mikle Garin Oct 02 '12 at 20:33
  • for (Rectangle rec : clippingAreas) { painter.repaint(rec); } – user1514195 Oct 04 '12 at 06:07
  • The problem is that if i do such a repaint on a simple example (like 3-4 overlaying components) - everything will be repainted as it should be. So i'd offer you to search for problems in your code, there should be some kind of bug or atleast misundrestanding. – Mikle Garin Oct 04 '12 at 09:10
  • I just got one more idea about what you said - you say you repaint y[0-20] and y[620-640] areas. Do you have any component that intersects with them and lies between those areas? – Mikle Garin Oct 04 '12 at 09:11
  • I do have a component lying in between these two rectangles. That's the goal, to prevent my painter (animation), which lies beyond this component, from repainting, since its not shown anyway. If the painter would do, the animation in this area gets redrawn and "overpainted" by the menu as well (which does not even need to have a repaint). I want the menu to become repainted as soon as i want it to do - which triggers also the repaint of the animation, but that's far less to compute, since its only on interaction, not permanently. – user1514195 Oct 04 '12 at 09:51
  • Well, i guess you should really post an SSCCE (http://sscce.org/) for your problem. Otherwise its hard to reproduce the exact situation. – Mikle Garin Oct 04 '12 at 11:22
0

Ok there we go:

package animationissue;

import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;

public class AnimationIssue extends JFrame {

JPanel a = new JPanel() {

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        System.out.println("map has become repainted");
    }
};
JPanel b = new JPanel() {

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        System.out.println("menu as well");
    }
};

public AnimationIssue() {
    this.setSize(500, 500);
    this.setLayout(null);

    a.setSize(400, 400);
    b.setSize(400, 200);

    this.getLayeredPane().add(a, JLayeredPane.DEFAULT_LAYER); // Map
    this.getLayeredPane().add(b, JLayeredPane.PALETTE_LAYER); // Menu

    a.setLocation(0, 0);
    b.setLocation(0, 100);

    a.setBackground(Color.red);
    b.setBackground(Color.blue);



    Thread t = new Thread(new Runnable() {

        @Override
        public void run() {
            // doin some computations for animation
            // cast a repaint after having finished new 
            //animation information i.e. interpolation
            while (true) {
                try {
                    Thread.sleep(2000);
                } catch (Exception e) {
                }

                // case 1 - just a repaint of the whole component - triggering map redraw results in menu redraw
                // a.repaint();

                // case 2 - repaint of specified rectangle 
                // Either passing one - the menu does not get repainted, or passing       both - menu also gets repainted       

                //a.repaint(0, 0, 400, 100);
                //a.repaint(0, 300, 400, 100);

                // paintimmediately works for now

                //a.paintImmediately(0, 0, 400, 100);
                //a.paintImmediately(0, 300, 400, 100);

                // Just repainting Menu does not trigger map to become repainted, except if its opague, but then it should become repainted
                b.repaint();


            }

        }
    });

    t.start();
}

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
    // TODO code application logic here
    AnimationIssue f = new AnimationIssue();
    f.setVisible(true);
}

}

I was really looking forward to optimize the behavior, that no menu gets redrawn, if its not necessary. You have to imagine the menu of being a component holding several JLists with a lot of String drawing tasks, which really have a massive impact on cpu usage. I havent been wondering, since it gets redrawn approximately 25 times per sec.

I am just uncertain for the current solution, if using paintImmediately is proper. Apart from this - if you or somebody has an alternative - better - way to prevent useless redrawing (i really thought Glasspane or JLayeredPane or isOptimizedDrawing or isOpaque might help out), i am really thankful.

Best regards.