2015-06-16 116 views
13

我正试图实现以下“网格”布局。为什么java.awt.Graphics.drawLine异常缓慢?

CompleteImage

该类延伸java.awt.Canvas中,并在paint函数绘制这些形状(或行)。为什么画布?检查here,尝试做初步类似的事情。

更新MCVE代码用于获取上述“布局”:

import java.awt.BasicStroke; 
import java.awt.BorderLayout; 
import java.awt.Canvas; 
import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import javax.swing.JButton; 
import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.JPanel; 
import javax.swing.SwingConstants; 
import javax.swing.SwingUtilities; 
import javax.swing.UIManager; 

@SuppressWarnings("serial") 
public class SO_MCVE extends JPanel { 

    private DrawingCanvas _drawingCanvas = null; 

    private JButton repaintBtn; 

    public SO_MCVE() { 
     super(new BorderLayout()); 

     _drawingCanvas = new DrawingCanvas(); 
     _drawingCanvas.setSize(new Dimension(600, 600)); 

     JLabel repaintLabel = new JLabel(
       "<html><div style=\"text-align: center;\">" + 
       "REPAINT</html>"); 
     repaintLabel.setHorizontalAlignment(
       SwingConstants.CENTER); 

     repaintBtn = new JButton(); 
     repaintBtn.add(repaintLabel); 
     repaintBtn.addActionListener(new ActionListener() { 
      public void actionPerformed(ActionEvent e) { 

       _drawingCanvas.triggerRepaint(); 
      } 
     }); 

     add(_drawingCanvas, BorderLayout.CENTER); 
     add(repaintBtn, BorderLayout.PAGE_END); 
    } 

    private static void createAndShowGUI() { 
     JFrame frame = new JFrame("StackOverflow MCVE for drawLine"); 

     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.add(new SO_MCVE()); 
     frame.pack(); 
     frame.setVisible(true); 
    } 

    public static void main(String[] args) { 

     SwingUtilities.invokeLater(new Runnable() { 
      public void run() { 
       UIManager.put("swing.boldMetal", Boolean.FALSE); 
       createAndShowGUI(); 
      } 
     }); 
    } 
} 

@SuppressWarnings("serial") 
class DrawingCanvas extends Canvas { 

    public static final Color lightGreen = new Color(0, 255, 0, 180); 
    public static final BasicStroke STROKE1PX = new BasicStroke(1.0f); 
    public static final BasicStroke STROKE3PX = new BasicStroke(3.0f); 

    private static final int LEFT = 50; 
    private static final int RIGHT = 550; 
    private static final int TOP = 50; 
    private static final int BOTTOM = 550; 

    private static final double WIDTH = 500.00d; 
    private static final double HEIGHT = 500.00d; 

    public DrawingCanvas() { 

     setBackground(Color.BLACK); 
    } 

    public void paint(Graphics g) { 
     update(g); 
    } 

    public void triggerRepaint() { 
     repaint(); 
    } 

    public void update(Graphics g) { 

     Graphics2D g2 = (Graphics2D) g; 
     Dimension dim = getSize(); 
     int w = (int) dim.getWidth(); 
     int h = (int) dim.getHeight(); 

     // Clears the rectangle that was previously drawn 
     g2.setPaint(Color.BLACK); 
     g2.fillRect(0, 0, w, h); 

     drawLines(g2, w, h); 
    } 

    /** Draw the lines marking the x-y limits **/ 
    private void drawLines(Graphics2D g2, int w, int h) { 

     long start = System.nanoTime(); 
     System.out.println("Start of drawLines(): " + start); 

     // Thick lines 
     g2.setPaint(Color.GREEN); 
     g2.setStroke(STROKE3PX); 
     g2.drawLine(LEFT, 0, LEFT, h); 
     g2.drawLine(RIGHT, 0, RIGHT, h); 
     g2.drawLine(0, TOP, w, TOP); 
     g2.drawLine(0, BOTTOM, w, BOTTOM); 

     System.out.println("Done drawing thick lines!"); 
     long end = System.nanoTime(); 
     System.out.println("Time taken (ns): " + 
       (end - start) + ", Time taken(ms): " + 
       ((end - start)/1000/1000)); 
     start = end; 

     // Thin vertical lines 
     g2.setPaint(lightGreen); 
     g2.setStroke(STROKE1PX); 
     int wInc = ((int) WIDTH)/50; 
     for(int i = LEFT; i <= RIGHT; i += wInc) { 
      g2.drawLine(i, TOP, i, BOTTOM); 
     } 

     System.out.println("Done drawing vertical lines!"); 
     end = System.nanoTime(); 
     System.out.println("Time taken (ns): " + 
       (end - start) + ", Time taken(ms): " + 
       ((end - start)/1000/1000)); 
     start = end; 

     // Thin horizontal lines 
     g2.setPaint(lightGreen); 
     g2.setStroke(STROKE1PX); 
     int hInc = ((int) HEIGHT)/50; 
     for(int i = TOP; i <= BOTTOM; i += hInc) { 
      g2.drawLine(LEFT, i, RIGHT, i); 
     } 

     System.out.println("Done drawing horizontal lines!"); 
     end = System.nanoTime(); 
     System.out.println("Time taken (ns): " + 
       (end - start) + ", Time taken(ms): " + 
       ((end - start)/1000/1000)); 

     System.out.println(); 
    } 
} 

与上面的代码中示出的问题是,它是一段时间服用(大约3秒),以使这些线,每当我请致电repaint()
按下“重新绘制”按钮在MCVE中触发重绘。
直线将拿得出一个一个慢慢的,如下面的图所示:

Halfway

所以,问题是:
有什么理由drawLine是如此之慢?我试图在类似的for循环中使用g2.draw(某些Ellipse2D.Double ..)绘制同样多的(如果不是更多)椭圆,并且没有问题。

注意:使用jre1.7.0_25,视窗7时,Eclipse
使用System.nanoTime() 简单基准:

Done drawing thick lines! 
Time taken (ns): 8858966, Time taken(ms): 8 
Done drawing vertical lines! 
Time taken (ns): 3649188968, Time taken(ms): 3649 
Done drawing horizontal lines! 
Time taken (ns): 106730282, Time taken(ms): 106 

注:绘制 '细的垂直线' 是永远拿走!

UPDATE:
注意:使用jre1.8.0_11,Windows 7中,Eclipse的
简单标杆使用System.nanoTime():

Done drawing thick lines! 
Time taken (ns): 110027, Time taken(ms): 0 
Done drawing vertical lines! 
Time taken (ns): 185567, Time taken(ms): 0 
Done drawing horizontal lines! 
Time taken (ns): 195419, Time taken(ms): 0 

注意:使用jre1。 8.0_45,Windows 7,Eclipse
使用System.nanoTime()进行简单基准测试:

Done drawing thick lines! 
Time taken (ns): 6716121, Time taken(ms): 6 
Done drawing vertical lines! 
Time taken (ns): 2427676380, Time taken(ms): 2427 
Done drawing horizontal lines! 
Time taken (ns): 83030042, Time taken(ms): 83 

显然,jre1.8.0_11作品真的很好?

如何运行W上的不同版本的JRE(不知道我在做正确!):

VersionSwitch

谢谢!:)

回答

4

按照Michael Dibbets的建议使用缓冲。

我取代the SO_MCVE.update(Graphics g)方法与一个绘制到屏幕外缓冲区,然后绘制缓冲区:

public void update(Graphics g) { 

    Graphics2D g2 = (Graphics2D) g; 
    Dimension dim = getSize(); 
    int w = (int) dim.getWidth(); 
    int h = (int) dim.getHeight(); 

    // Create the buffer 
    BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); 
    Graphics2D ig2 = image.createGraphics(); 

    // Paint everythign on the buffer 
    // Clears the rectangle that was previously drawn 
    ig2.setPaint(Color.BLACK); 
    ig2.fillRect(0, 0, w, h); 

    drawLines(ig2, w, h); 

    // Paint the buffer 
    g2.drawImage(image, 0, 0, null); 
} 

drawLines()的开始:1832687816359773个
完成绘制粗线!
所用时间(ns):2212913,耗时(ms):2
完成绘制垂直线条!
所用时间(ns):37676442,耗时(ms):37
完成绘制水平线!
所用时间(纳秒):6453455,所用时间(毫秒):6

如果你想成为更有效,你可以缓冲图像存储为一个属性和替换它,如果尺寸变化。

这就是说,绘制垂直线需要较长的时间才能达到水平。在用调试器和分析器深入研究SunGraphics2D的内部之后,我仍然无法解释它。

+0

您好,感谢您抽出宝贵时间来回答我的问题!我会尽快尝试。 :) – Gosu

+2

谢谢,解决方案很好。我对**仍然很好奇**但是为什么“绘制垂直线条”需要更长时间**但是您的答案确实解决了我的问题,所以我已经接受了它。谢谢! :) – Gosu

+2

可能是我们的机器上的java2d版本中的一个错误...(我在Windows 8上使用了JDK1.7.0_60)。当我使用JVisualVM CPU分析器时,我发现只绘制垂直线,有25050次调用sun.java2d.loops.Blit $ GeneralMaskBlit.Blit(...),占用几乎所有的绘图时间。 Google搜索找不到关于此特定问题的任何信息。 –

4

似乎这是导致此行为的alpha in Color lightGreen = new Color(0, 255, 0, 180);

删除alpha将立即渲染线条。如果你需要alpha,那么一个解决方案是调整渲染提示。

public void update(Graphics g) { 

    Graphics2D g2 = (Graphics2D) g; 
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 

// ... 
+0

嗨,这是一个有趣的观察。但是,为什么* alpha *仅影响垂直线?它与图形的呈现方式有关吗? – Gosu

+0

删除alpha时需要+1,它需要0ms ...我认为@ Marco13对上述问题的评论也是正确的,因为软件渲染速度很慢。当我保持alpha并添加VM参数-Dsun.java2d.opengl = true时,它也变为0ms。问题仍然是为什么使用alpha渲染的软件需要很长时间,但对于我来说,它仍然像是软件渲染器中的一个错误。 –

+0

是的,在渲染器中肯定有一个关于半透明的bug。 –