The Java Tutorials' Weblog

pageicon Monday Jul 09, 2007

Custom Painting: Is This Fast Enough?

The following program is from the latest version of the "Custom Painting in Swing" lesson, which will be released sometime near the end of the month. For those who might think that Swing GUI rendering is slow, this lesson should show that does not have to be the case! The following program draws an object to the screen; it follows your mouse as you drag it around. When you try this out, maximize the application (we want to occupy as much of the screen as possible), drag the mouse around, and note its performance. How is the rendering speed? Is it quick? Does it "feel" similar to running a GUI written in a native language? Would this performance be fast enough for your own applications? If you look closely at the source you will notice that we are using the multi-arg version of repaint to intelligently repair only the bits that have changed. Also, can you tell why we are invoking repaint two times in the same method? If not, uncomment the first invocation and run it again. Do you understand what is happening here? Useful comments to post here would be 1) your perception of the GUI's performance and 2) details about the system you are running it on.

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.BorderFactory;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics; 
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseMotionAdapter;

public class SwingDemo {
    
    public static void main(String[] args) {
        JFrame f = new JFrame("Swing Demo");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        f.add(new MyPanel());
        f.pack();
        f.setVisible(true);
    }
}

class MyPanel extends JPanel {

    RedSquare redSquare = new RedSquare();

    public MyPanel() {

        setBorder(BorderFactory.createLineBorder(Color.black));

        addMouseListener(new MouseAdapter(){
            public void mousePressed(MouseEvent e){
                moveSquare(e.getX(),e.getY());
            }
        });

        addMouseMotionListener(new MouseAdapter() {
            public void mouseDragged(MouseEvent e) {
                moveSquare(e.getX(),e.getY());
            }
        });

    }

    private void moveSquare(int x, int y){

        final int CURR_X = redSquare.getX();
        final int CURR_Y = redSquare.getY();
        final int CURR_W = redSquare.getWidth();
        final int CURR_H = redSquare.getHeight();
        final int OFFSET = 1;

        if ((CURR_X!=x) || (CURR_Y!=y)) {
            repaint(CURR_X,CURR_Y,CURR_W+OFFSET,CURR_H+OFFSET);
            redSquare.setX(x);
            redSquare.setY(y);
            repaint(redSquare.getX(), redSquare.getY(), 
                    redSquare.getWidth()+OFFSET, 
                    redSquare.getHeight()+OFFSET);
        }
    }

    public Dimension getPreferredSize() {
        return new Dimension(250,200);
    }
    
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);       
        g.drawString("This is my custom Panel!",10,20);
        redSquare.paintSquare(g);
    }  
}

class RedSquare {

    private int xPos = 50;
    private int yPos = 50;
    private int width = 20;
    private int height = 20;

    public void setX(int xPos){ 
        this.xPos = xPos;
    }

    public int getX(){
        return xPos;
    }

    public void setY(int yPos){
        this.yPos = yPos;
    }

    public int getY(){
        return yPos;
    }

    public int getWidth(){
        return width;
    } 

    public int getHeight(){
        return height;
    }

    public void paintSquare(Graphics g){
        g.setColor(Color.RED);
        g.fillRect(xPos,yPos,width,height);
        g.setColor(Color.BLACK);
        g.drawRect(xPos,yPos,width,height);  
    }
}

July 12 Update

Here is the latest version, based on some comments received by Swing engineering. This change uses SwingUtilities to build the GUI on the Event Dispatch Thread (EDT). Since AWT and Swing event handlers are by default notified on the EDT, we can leave the moveSquare method as it was. You can check this by invoking SwingUtilities.isEventDispatchThread inside the method and seeing that it returns true. This version also rolls the RedSquare code back into the MyPanel class. If you have lots of objects to maintain in your own app, you may want to factor that code into its own class (like in the first version); I'll probably include that as an exercise at the end of the lesson.

-- Scott Hommel
import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.BorderFactory;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics; 
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseMotionAdapter;

public class SwingDemo {
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable(){
            public void run(){
                createAndShowGUI(); 
            }
        });
    }

    private static void createAndShowGUI() {
        JFrame f = new JFrame("Swing Demo");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        f.add(new MyPanel());
        f.pack();
        f.setVisible(true);
    } 
}

class MyPanel extends JPanel {

    private int squareX = 50;
    private int squareY = 50;
    private int squareW = 20;
    private int squareH = 20;

    public MyPanel() {

        setBorder(BorderFactory.createLineBorder(Color.black));

        addMouseListener(new MouseAdapter(){
            public void mousePressed(MouseEvent e){
                moveSquare(e.getX(),e.getY());
            }
        });

        addMouseMotionListener(new MouseAdapter() {
            public void mouseDragged(MouseEvent e) {
                moveSquare(e.getX(),e.getY());
            }
        });

    }

    private void moveSquare(final int x, final int y){
                final int OFFSET = 1;
                if ((squareX!=x) || (squareY!=y)) {
                    repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
                    squareX=x;
                    squareY=y;
                    repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
                } 
    }

    public Dimension getPreferredSize() {
        return new Dimension(250,200);
    }
    
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);       
        g.drawString("This is my custom Panel!",10,20);
        g.setColor(Color.RED);
        g.fillRect(squareX,squareY,squareW,squareH);
        g.setColor(Color.BLACK);
        g.drawRect(squareX,squareY,squareW,squareH);
    }  
}
Comments:

Runs like Jan Ulrich ;-)

Posted by Ralf on July 10, 2007 at 12:05 AM PDT #

Runs excellent on my Pentium M 1.5Ghz, SIS M661MX Video-card (no top-tech here). I even tried (modifying the program) to handle images and the response was impressive. Definitely "feels" like a native language GUI.

Posted by sdecima on July 10, 2007 at 08:20 AM PDT #

You should repaint the union of the two squares (old and new) instead of doing two repaints.

Posted by Mikael Grev on July 10, 2007 at 09:53 AM PDT #

That is at least if you do not want to rely on the RepaintManager doing the unioning for you, which it normally will, but not if it is interrupted in between maybe.

Posted by Mikael Grev on July 10, 2007 at 09:58 AM PDT #

Hi Mikael, It's actually totally fine to invoke repaint twice in the same event handler; Swing will figure out what needs to be repaired based on those invocations and will repaint the component just once. Thanks to everyone who is testing this out. Glad to see it's performing well on your systems!

Posted by Scott Hommel on July 10, 2007 at 10:53 AM PDT #

Hello Scott

Please don't forget to call all Swing code from EventDispatchThread
the recommended way is to add a static createGui method
and call it from main() with help of SwingUtilities.invokeLater

alexp
Swing team

Posted by Alexander Potochkin on July 10, 2007 at 11:56 AM PDT #

Just my comments: I have been writing highly-graphical apps in java for years. Apps where the user graphically lays out hundreds of objects of the screen; moving them around, etc. As long as you use the repaint manager to optimize your painting (which any good graphics app does regardless of what language it's written in), they're always speedy. I wish more people would utilize this and their apps would feel much quicker. This would help eliminate the erroneous statement that java is slow (but performance-wise it is less forgiving). I suspect this technique simply isn't stressed enough/documented sufficiently. Glad the word is getting out.

Posted by Mike on July 10, 2007 at 12:08 PM PDT #

Scott, you're correct. I didn't see the "private" declaration on the method. The means you can't call it from outside and you don't need to be afraid that it will not be called from the EDT. My bad.

Posted by Mikael Grev on July 10, 2007 at 12:52 PM PDT #

Alex, ah yes, I will add that code to the final version. Thanks! Mike: One thing that the lesson will show is that if you comment out that first invocation, all previous squares can remain on the screen and the performance stays the same since the clip is set so small. :)

Posted by Scott Hommel on July 10, 2007 at 01:33 PM PDT #

It took me a minute to figure out what happens here, but it is fast indeed (P4@2.8GHz).

Posted by Tom on July 10, 2007 at 11:56 PM PDT #

I do not see any difference if I do just simple repaint like it is shown below.
if ((CURR_X!=x) || (CURR_Y!=y)) {
            redSquare.setX(x);
            redSquare.setY(y);
            repaint();
}
I have 1.7Ghz CPU, RADEON 9000, Windows XP. Isn't the sample too simple to make any conclusions about performance? For human it does not make sense if painting was done in 0.0003 seconds or 0.03 seconds hovewer the difference is 100 times. To feel the difference you probably need to demonstrate more sophisticated samples and even better if your had 2 samples: one which uses "advanced" technique and another witch not. But in case you wish to compare with native painting some native painting's measurement's are necessary.

Posted by Maxim Zakharenkov on July 11, 2007 at 01:35 AM PDT #

Hi Maxim, it's good to know that the no-arg version of repaint runs fine for you. Your system is capable of painting the entire surface area. That's good news for Swing. But if you want to make painting as effecient as possible (maybe your customer's machines won't be as fast?) then you'll want to know how to invoke the multi-arg version of repaint. So we always recommend repainting with multiple arguments. To any readers that want to dream up a scenario that shows something running faster with one versus the other, feel free to post! The fact that even the no-arg version of repaint runs fast for you definitely says something good about Swing!

Posted by Scott Hommel on July 11, 2007 at 02:57 PM PDT #

Ah, that's clever – only repairing the bits that have changed. Why didn't I think of that? Sheesh, of course. This is why I love the internet – when I've got a problem, I simply search for the problem and have a solution. Or, I come here to these blogs, and I find a solution. It's brilliant – Kudo's to you guys who are so happy to publish this and information and these resources on the internet!

Posted by online shopping on July 03, 2008 at 02:13 AM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed

« November 2009
SunMonTueWedThuFriSat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
     
       
Today

Feeds

Search this blog

Links

Weblog menu

Today's referrers

Today's Page Hits: 783