Java Swing:drawLine非常缓慢

3
我正在使用swing编写一个java应用程序,需要在一个正方形上方绘制一个网格。为此,我使用了Graphics类提供的drawLine(...)方法。除了每条线都需要很长时间来绘制(50条线需要超过20秒...),其他一切都运行良好。我甚至可以实时看到线条的绘制过程。一个奇怪的问题是,水平线比垂直线绘制得快得多(几乎瞬间完成)。
我可能做错了什么。以下是网格的代码。
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);
    }
}

5
因为您没有使用双缓冲技术。请参考http://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html。 - Suresh Kumar
当然,我怎么会这么傻。谢谢! - Nicolas
3
@SureshKumar 默认情况下,Swing 组件会进行双缓冲处理... - kleopatra
@SureshKumar 同意 kleopatra 的观点 - 仅凭这部分代码无法确定他是否使用了双缓冲绘图策略。而且,所呈现的代码完全没有问题。 - Mikle Garin
2
单缓冲永远不会花那么长时间。 - Jarrod Smith
@MikleGarin和kleopatra:它们应该默认为双缓冲,但即使手动将双缓冲设置为true也没有改变结果。手动绘制可能不是双缓冲吗? JarrodSmith:至于时间,也许我正在使用运行在Linux上的旧型净书解释了这一点? - Nicolas
2个回答

4
您发布的代码没有问题,可以正常运行。
这里是一个使用您的方法制作的组件工作示例(有些简化):
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 );
        }
    }
}

我猜这段代码外面可能有些问题。

顺便说一句,我建议您计算绘画区域的可见部分(使用JComponent的getVisibleRect()方法或Graphics g.getClip().getBounds()方法),并将您的绘画限制在该区域内。如果组件非常大(例如10000x10000像素的组件区域),这个小优化可以加速组件的绘画。


我认为除了那段代码以外没有任何问题,我做的和你差不多。现在我用getVisibleRect().getSize()替换了getWidth()。请看下面的解决方案。 - Nicolas

-1

这是我使用双缓冲解决问题的方法,正如@sureshKumar所建议的那样。我只是在离屏图像上绘制,并在绘制完成后简单地调用drawImage()。这似乎起到了作用。

编辑:这似乎只有在您想从paintComponent(...)方法之外调用您的绘画方法(在我的情况下为drawGrid())时才有用。

这是代码:

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);
    }
}

附言:如果这应该作为我的问题的编辑添加,请告诉我,我会这样做。


耶稣,为什么你不直接扩展一个已经有双缓冲策略的JComponent(实际上每个Swing组件都有并默认使用它)?这将节省很多时间,而且你不需要发明轮子...当你扩展一个JComponent时,你所需要做的就是扩展paintComponent方法和...是的,绘制!就这样。甚至不需要担心双缓冲。 - Mikle Garin
还有一件事 - 当我提到使用 getVisibleRect() 方法来避免无意义的绘制时,你似乎没有理解我的意思。我是在谈论排除那些在组件上不可见的线段(和线段的部分)。例如,如果您的组件包含在活动滚动区域内 - 您每次只能看到其中的一部分。当可能时,应避免在该可见部分之外进行绘制。在您的情况下,可以轻松地排除不可见的线条。 - Mikle Garin
@MikleGarin 我现在明白问题所在了。实际上,我是从另一个函数中调用了 drawGrid(Graphics g),但我相信只有 painComponent(Graphics g) 会自动进行双缓冲,而不是使用 JComponent 的 Graphics 绘制的所有内容。无论如何,如果我可以从 paintComponent(...) 外部调用 drawGrid(...) 方法,那么它将变得更容易,因此我将保留我的实现。感谢您对 getVisibleRect() 的评论,我现在理解了您的观点。 - Nicolas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接