3

I am writing a java application using swing in which I need to draw a grid above a square. In order to do so, I am using the drawLine(...) method provided by the Graphics class.

Everything works fine except that it takes a lot of time to draw each line (more than 20 sec for 50 lines...). I can even see the lines being drawn in real time. One weird thing is that the horizontal lines are drawn way faster than the vertical lines (almost instantly).

I might be doing something wrong. Here is the code for the grid.

public void drawGrid(Graphics g){
    g.setColor(new Color(255, 255, 255, 20));
    int width = getWidth();
    int height = (int) (width * Utils.PLATE_RATIO);
    int step = pixelSize*gridSpacing;
    Color bright = new Color(255, 255, 255, 100);
    Color transparent = new Color(255, 255, 255, 20);
    for(int ix = insets.left + step;                        
            ix < width; ix += step){
        if(((ix - insets.left) / step) % 10 == 0){
            g.setColor(bright);
        }
        else{
            g.setColor(transparent);
        }
        g.drawLine(ix, insets.top, ix, height+insets.top);
    }
    for(int iy = insets.top+step;
            iy < (insets.top + height); iy += step){
        if(((iy - insets.top) / step) % 10 == 0){
            g.setColor(bright);
        }
        else{
            g.setColor(transparent);
        }
        g.drawLine(insets.left, iy, width + insets.left, iy);
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
Nicolas
  • 53
  • 5
  • 5
    It is because you are not using double buffering. See http://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html – Suresh Kumar Oct 24 '12 at 06:21
  • Of course, how can I be so stupid. Thank you ! – Nicolas Oct 24 '12 at 06:57
  • 3
    @SureshKumar swing components are double-buffering by default ... – kleopatra Oct 24 '12 at 09:42
  • @SureshKumar agreed with kleopatra - you cannot say if he is using double-buffered painting strategy or not by just this part of code. And the presented code is totally fine. – Mikle Garin Oct 24 '12 at 09:49
  • 2
    Single buffering would never take that long. – sleep Oct 24 '12 at 09:51
  • @MikleGarin and kleopatra : they are supposed to be double-buffered by default, but even setting the double-buffering manually to true didn't change the result. Is it possible that manual drawing is not double-buffered? JarrodSmith : as for the time, maybe the fact that I am working on an old netbook running on linux explains that? – Nicolas Oct 25 '12 at 02:24

2 Answers2

4

The code you have posted is fine, there is no problems in it.
Here is a working example of a component using your method (a bit simplified):

public static class MyGrid extends JComponent
{
    private int step = 10;

    public MyGrid ()
    {
        super ();
    }

    public Dimension getPreferredSize ()
    {
        return new Dimension ( 500, 500 );
    }

    protected void paintComponent ( Graphics g )
    {
        super.paintComponent ( g );
        drawGrid ( g );
    }

    public void drawGrid ( Graphics g )
    {
        int width = getWidth ();
        int height = getHeight ();
        Color bright = new Color ( 255, 255, 255, 200 );
        Color transparent = new Color ( 255, 255, 255, 100 );

        for ( int ix = step; ix < width; ix += step )
        {
            if ( ( ix / step ) % 10 == 0 )
            {
                g.setColor ( bright );
            }
            else
            {
                g.setColor ( transparent );
            }
            g.drawLine ( ix, 0, ix, height );
        }

        for ( int iy = step; iy < height; iy += step )
        {
            if ( ( iy / step ) % 10 == 0 )
            {
                g.setColor ( bright );
            }
            else
            {
                g.setColor ( transparent );
            }
            g.drawLine ( 0, iy, width, iy );
        }
    }
}

I guess there is some problem outside that piece of code.

P.S. A bit an offtopic but...

I suggest you to calculate the visible part of the painting area (using either JComponent's getVisibleRect () method or Graphics g.getClip ().getBounds () method) and limit your paintings with only that area.

That small optimization could speedup component's painting in times if it is really large (for example with 10000x10000 pixels component's area).

Mikle Garin
  • 10,083
  • 37
  • 59
  • I don't think there was any problem outside that code, I did more or less the same as you. I now replaced `getWidth()` by `getVisibleRect().getSize()`. See my solution below. – Nicolas Oct 25 '12 at 01:35
-1

Here is how I solved the problem using Double-Buffering, as advised by @sureshKumar. I am simply drawing on an offscreen image and simply calling drawImage() when the drawing is over. This seems to do the trick.

Edit: this seems to be useful only if you want to call your painting methods (in my case drawGrid()) from outside of the paintComponent(...) method.

Here is the code:

private Graphics bufferGraphics;
private Image offScreen;
private Dimension dim;
//other attributes not shown...

public CentralPanel(){
    //Some initialization... (not shown)

    //I added this listener so that the size of my rectangle
    //and of my grid changes with the frame size
    this.addComponentListener(new ComponentListener() {

        @Override
        public void componentResized(ComponentEvent e) {
            dim = getVisibleRect().getSize();
            offScreen = createImage(dim.width, dim.height);
            bufferGraphics = offScreen.getGraphics();
            repaint();
            revalidate();
        }
        //other methods of ComponentListener not shown
    });
}

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.drawImage(offScreen, 0, 0, null);
}

public void drawGrid(){
    int width = dim.width - insets.left - insets.right;
    width -= (width % plateSize.getXSize());
    int height = (int) (width*Utils.PLATE_RATIO);
    height -= (height % plateSize.getYSize());
    int step = pixelSize*gridSpacing;
    Color bright = new Color(255, 255, 255, 100);
    Color transparent = new Color(255, 255, 255, 20);
    for(int ix = insets.left + step;                        
            ix < (width+insets.left); ix += step){
        if(((ix - insets.left) / step) % 10 == 0){
            bufferGraphics.setColor(bright);
        }
        else{
            bufferGraphics.setColor(transparent);
        }
        //I am now drawing on bufferGraphics instead 
        //of the component Graphics
        bufferGraphics.drawLine(ix, insets.top, ix, height+insets.top);
    }
    step *= Utils.PLATE_RATIO;
    for(int iy = insets.top+step;
            iy < (insets.top + height); iy += step){
        if(((iy - insets.top) / step) % 10 == 0){
            bufferGraphics.setColor(bright);
        }
        else{
            bufferGraphics.setColor(transparent);
        }
        bufferGraphics.drawLine(insets.left, iy, width + insets.left, iy);
    }
}

P.S.: If this should be added as an edit to my question, please tell me and I'll do it.

Nicolas
  • 53
  • 5
  • Jesus, why wouldn't you just extend a JComponent which already has a double-buffering strategy (actually every Swing component has it and uses it by default)? That will save a lot of your time and you won't need to invent a wheel... All you need to do when you extend a JComponent is to extend paintComponent method and... yes, paint! Thats all. No need to even bother about the double-buffering. – Mikle Garin Oct 25 '12 at 07:52
  • One more thing - you didn't get what i was talking about when i mentioned using `getVisibleRect ()` method to avoid meaningless painting. I was talking about excluding the lines (and those parts of lines) which are NOT visible on the component. For example if your component is contained inside an active scroll - you will only see a part of it at a time. You should avoid painting outside that visible part when it is possible. In your case you could easily exclude the lines which aren't visible. – Mikle Garin Oct 25 '12 at 07:54
  • @MikleGarin I understood what was the issue now. I was actually calling `drawGrid(Graphics g)` from another function, but I believe only the `painComponent(Graphics g)` is automatically double-duffered, and not everything painted with the JComponent's Graphics. Anyway, it makes it way easier if I can call the `drawGrid(...)` method from outside `paintComponent(...)`, so I will keep my implementation. Thanks for the comment on the `getVisibleRect()`, I got you point now. – Nicolas Oct 25 '12 at 09:19