2010-12-13 46 views
17

编辑两个Java:如何在Swing中实现双缓冲?

为了防止尖刻的评论和一个在线回答切中要害:IFF它就像调用简单setDoubleBuffered(真),那么我怎么获取当前离线缓冲所以我可以开始搞BufferedImage的底层像素databuffer?我很花时间写一段代码(这看起来还挺有趣),所以我真的很感谢回答实际回答(真是令人震惊;)我的问题和解释什么/这是如何工作,而不是一个和衬里的评论;)

这是一个代码工作,反弹一个正方形跨JFrame。我想知道可以用来转换这段代码的各种方式,以便它使用双缓冲。

请注意,我清除屏幕并重新绘制正方形的方式并不是最有效的方式,但这实际上不是这个问题的关键所在(从某种意义上来说,为了这个例子它有点慢)。

基本上,我需要不断修改BufferedImage中的很多像素(因为有某种动画),而且我不想看到由于屏幕上的单缓冲而导致的视觉失真。

我有一个JLabel的图标是一个ImageIcon包装BufferedImage。我想修改那个BufferedImage。

需要做些什么以使其变为双缓冲?

据我所知,在某种程度上“图像1”将显示,而我将“图像2”来绘制。但是一旦我画完了“image 2”,我该如何“快速”取代“image 1” by “image 2”

这是我应该手动做的事情,比如说,通过交换JLabel的ImageIcon自己?

我应该总是在相同的BufferedImage中绘图,然后在JLabel的ImageIcon的BufferedImage中执行BufferedImage像素的快速'blit'? (我猜不,我不知道该如何将它与显示器的“垂直空白线”同步[或者与平板显示器相当:我的意思是,在不干扰显示器本身刷新其时刻的情况下'同步'像素,以防止剪切])。

“repaint”命令怎么样?我想自己触发这些吗?哪个/何时应该打电话repaint()或其他?

最重要的要求是我应该直接在图像的像素databuffer中修改像素。

import javax.swing.*; 
import java.awt.event.WindowAdapter; 
import java.awt.event.WindowEvent; 
import java.awt.image.BufferedImage; 
import java.awt.image.DataBufferInt; 

public class DemosDoubleBuffering extends JFrame { 

    private static final int WIDTH = 600; 
    private static final int HEIGHT = 400; 

    int xs = 3; 
    int ys = xs; 

    int x = 0; 
    int y = 0; 

    final int r = 80; 

    final BufferedImage bi1; 

    public static void main(final String[] args) { 
     final DemosDoubleBuffering frame = new DemosDoubleBuffering(); 
     frame.addWindowListener(new WindowAdapter() { 
      public void windowClosing(WindowEvent e) { 
       System.exit(0); 
      } 
     }); 
     frame.setSize(WIDTH, HEIGHT); 
     frame.pack(); 
     frame.setVisible(true); 
    } 

    public DemosDoubleBuffering() { 
     super("Trying to do double buffering"); 
     final JLabel jl = new JLabel(); 
     bi1 = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB); 
     final Thread t = new Thread(new Runnable() { 
      public void run() { 
       while (true) { 
        move(); 
        drawSquare(bi1); 
        jl.repaint(); 
        try {Thread.sleep(10);} catch (InterruptedException e) {} 
       } 
      } 
     }); 
     t.start(); 
     jl.setIcon(new ImageIcon(bi1)); 
     getContentPane().add(jl); 
    } 

    private void drawSquare(final BufferedImage bi) { 
     final int[] buf = ((DataBufferInt) bi.getRaster().getDataBuffer()).getData(); 
     for (int i = 0; i < buf.length; i++) { 
      buf[i] = 0xFFFFFFFF; // clearing all white 
     } 
     for (int xx = 0; xx < r; xx++) { 
      for (int yy = 0; yy < r; yy++) { 
       buf[WIDTH*(yy+y)+xx+x] = 0xFF000000; 
      } 
     } 
    } 

    private void move() { 
     if (!(x + xs >= 0 && x + xs + r < bi1.getWidth())) { 
      xs = -xs; 
     } 
     if (!(y + ys >= 0 && y + ys + r < bi1.getHeight())) { 
      ys = -ys; 
     } 
     x += xs; 
     y += ys; 
    } 

} 

编辑

这是不是一个全屏幕的Java应用程序,而是一个普通的Java应用程序在其自己的(有点小)窗口中运行。

+2

Swing默认为双缓冲。这不是AWT。 – camickr 2010-12-13 15:47:27

+0

仍在等待某人发布我们可以测试的实际代码,以了解双缓冲是否值得。 – camickr 2010-12-14 04:18:26

+1

我想你已经误解了摇摆。您无法访问(未修改的)脱机缓冲区。 Swing会通过缓冲区到您的父级,并且您的父级会修改它。 ...要做你想做的事情,你必须*禁用* swing doublebuffering,并用BufferedImage做。如果你想要做真正的“bitblt”,你必须在AWT中使用“重量级”组件。 – 2012-02-02 09:28:34

回答

3

一般我们使用Canvas类,它适用于Java中的动画。安美居,以下是你如何实现双缓冲:

class CustomCanvas extends Canvas { 
    private Image dbImage; 
    private Graphics dbg; 
    int x_pos, y_pos; 

    public CustomCanvas() { 

    } 

    public void update (Graphics g) { 
    // initialize buffer 
    if (dbImage == null) { 

     dbImage = createImage (this.getSize().width, this.getSize().height); 
     dbg = dbImage.getGraphics(); 

    } 

    // clear screen in background 
    dbg.setColor (getBackground()); 
    dbg.fillRect (0, 0, this.getSize().width, this.getSize().height); 

    // draw elements in background 
    dbg.setColor (getForeground()); 
    paint (dbg); 

    // draw image on the screen 
    g.drawImage (dbImage, 0, 0, this); 
    } 

     public void paint (Graphics g) 
{ 

     g.setColor (Color.red); 



     g.fillOval (x_pos - radius, y_pos - radius, 2 * radius, 2 * radius); 

    } 
} 

现在,您可以更新从一个线程,然后是“重绘” x_pos上和Y_POS画布对象调用。同样的技术也应该在JPanel上工作。

+0

@Usman Saleem:Hi Saleem,很高兴看到一个新人帮助:)当使用Canvas时,我是否也可以像使用BufferedImage一样访问像素的databuffer? – SyntaxT3rr0r 2010-12-13 15:51:28

+0

不应该在美国东部时间发布重新打电话吗? – Riduidel 2010-12-13 15:51:34

+1

但是这不需要。 Swing也允许动画。这不是双缓冲。或者,如果它是可以在Swing JPanel上使用的相同代码,因为您所做的只是绘制图像。 – camickr 2010-12-13 15:53:16

11

----编辑每像素设置,以解决----

该项目一击解决了双缓冲,也有如何获得像素为BufferedImage的问题。

如果您在BufferedImage调用

WriteableRaster raster = bi.getRaster() 

它会返回一个WriteableRaster。从那里,你可以使用

int[] pixels = new int[WIDTH*HEIGHT]; 
// code to set array elements here 
raster.setPixel(0, 0, pixels); 

请注意,你可能会想优化代码,实际上不是为每个渲染的新数组。另外,你可能想要优化数组清除代码以不使用for循环。

Arrays.fill(pixels, 0xFFFFFFFF); 

可能会超越你的循环设置背景为白色。

----反应后可进行编辑----

关键是在你原来的JFrame的设置和运行呈现循环中。

首先,你需要告诉SWING停止Rasterizing,只要它想要;因为,当你完成绘制到你想要换出的缓冲图像时,你会告诉它。用JFrame的做法

setIgnoreRepaint(true); 

然后,您需要创建缓冲策略。基本上,它指定你想要多少缓冲区使用

createBufferStrategy(2); 

现在你试图创建缓冲策略,你需要获取BufferStrategy对象你会需要它以后切换缓冲区。

final BufferStrategy bufferStrategy = getBufferStrategy(); 

里面你Thread修改run()循环包含:

... 
    move(); 
    drawSqure(bi1); 
    Graphics g = bufferStrategy.getDrawGraphics(); 
    g.drawImage(bi1, 0, 0, null); 
    g.dispose(); 
    bufferStrategy.show(); 
... 

图形从BufferStrategy中抓起将关闭屏幕Graphics对象,创建三重缓冲时,这将是“下一个“以循环方式在屏幕外Graphics对象。

图像和Graphics上下文在遏制场景中不相关,并且您告诉Swing您会自己绘制图形,因此您必须手动绘制图像。这并不总是一件坏事,因为当图像被完全绘制时(而不是在之前),您可以指定缓冲区翻转。

处理图形对象只是一个好主意,因为它有助于垃圾收集。显示bufferStrategy将翻转缓冲区。

虽然在上面的代码中可能有某处出现了失误,但这应该会让您有90%的选择。祝你好运!

----原帖如下----

它可能看起来很可笑是指这样一个问题,一个JavaSE的教程,但你看着BufferStrategy and BufferCapatbilites

我认为你遇到的主要问题是你被图像的名称所愚弄。 A BufferedImage与双缓冲无关,它与“缓冲内存中的数据(通常来自磁盘)”有关。“因此,如果您希望拥有“双缓冲映像”,则需要两个BufferedImages;因为改变正在显示的图像中的像素是不明智的(这可能会导致重新绘制问题)。

在您的渲染代码中,您抓取图形对象。如果您根据上述教程设置了双缓冲,这意味着您将抓取(默认情况下)屏幕外的Graphics对象,并且所有图形将在屏幕外。然后,您将您的图像(当然是正确的)绘制到屏幕外的对象。最后,你将策略告诉show()这个缓冲区,它会为你替换Graphics环境。

+0

@Edwin Buck:+1帮助,但我不使用BufferedImage,因为这个名字会愚弄我:我使用它们是因为在图像中直接操作像素的能力是一种要求,也是我知道的唯一方法访问图像的底层像素databuffer是使用BufferedImage。 – SyntaxT3rr0r 2010-12-13 16:57:23

+0

@SpoonBender好吧,我觉得你需要两个BufferedImages,实际上你只需要一个,因为你需要分别绘制到每个缓冲区。我更新了我的文章,详细介绍了如何将Swing的双缓冲支持集成到您的示例中。 – 2010-12-13 17:10:07

+0

这也许是我发布的一个更好的策略。同样的技术也被Canvas类用于多个缓冲区(我认为这是我很久以前在我的Java学生的动画项目中使用的技术)。 – 2010-12-13 17:28:23

3

以下是所有绘图发生在event dispatch thread上的变体。

附录:

基本上,我需要不断地修改许多像素的BufferedImage ...

kinetic model说明了几种方法来像素的动画。

import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.EventQueue; 
import java.awt.Graphics2D; 
import java.awt.GridLayout; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import javax.swing.*; 
import java.awt.image.BufferedImage; 

/** @see http://stackoverflow.com/questions/4430356 */ 
public class DemosDoubleBuffering extends JPanel implements ActionListener { 

    private static final int W = 600; 
    private static final int H = 400; 
    private static final int r = 80; 
    private int xs = 3; 
    private int ys = xs; 
    private int x = 0; 
    private int y = 0; 
    private final BufferedImage bi; 
    private final JLabel jl = new JLabel(); 
    private final Timer t = new Timer(10, this); 

    public static void main(final String[] args) { 
     EventQueue.invokeLater(new Runnable() { 

      @Override 
      public void run() { 
       JFrame frame = new JFrame(); 
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
       frame.add(new DemosDoubleBuffering()); 
       frame.pack(); 
       frame.setVisible(true); 
      } 
     }); 
    } 

    public DemosDoubleBuffering() { 
     super(true); 
     this.setLayout(new GridLayout()); 
     this.setPreferredSize(new Dimension(W, H)); 
     bi = new BufferedImage(W, H, BufferedImage.TYPE_INT_ARGB); 
     jl.setIcon(new ImageIcon(bi)); 
     this.add(jl); 
     t.start(); 
    } 

    @Override 
    public void actionPerformed(ActionEvent e) { 
     move(); 
     drawSquare(bi); 
     jl.repaint(); 
    } 

    private void drawSquare(final BufferedImage bi) { 
     Graphics2D g = bi.createGraphics(); 
     g.setColor(Color.white); 
     g.fillRect(0, 0, W, H); 
     g.setColor(Color.blue); 
     g.fillRect(x, y, r, r); 
     g.dispose(); 
    } 

    private void move() { 
     if (!(x + xs >= 0 && x + xs + r < bi.getWidth())) { 
      xs = -xs; 
     } 
     if (!(y + ys >= 0 && y + ys + r < bi.getHeight())) { 
      ys = -ys; 
     } 
     x += xs; 
     y += ys; 
    } 
} 
3

在Swing窗口模式下,你想要的基本上是不可能的。对于窗口重绘的光栅同步不支持,这仅在全屏模式下可用(甚至可能不被所有平台支持)。

默认情况下,Swing组件是双缓冲的,也就是说,它们会将所有渲染都渲染到中间缓冲区,然后该缓冲区最终被复制到屏幕上,避免从背景清除闪烁,然后在其上绘制。 这就是在所有底层平台上合理良好支持的唯一策略。它避免了只重绘闪烁,但不会从移动的图形元素看到视觉撕裂。

在您控制下完全访问某个区域的原始像素的相当简单的方法是从JComponent扩展自定义组件并覆盖其paintComponent()方法以从BufferedImage(从内存中)绘制区域, :

public class PixelBufferComponent extends JComponent { 

    private BufferedImage bufferImage; 

    public PixelBufferComponent(int width, int height) { 
     bufferImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 
     setPreferredSize(new Dimension(width, height)); 
    } 

    public void paintComponent(Graphics g) { 
     g.drawImage(bufferImage, 0, 0, null); 
    } 

} 

然后,您可以操纵您缓存的图像,无论您希望如何。要让您的更改在屏幕上显示,只需在其上调用repaint()即可。如果您从EDT以外的线程进行像素操作,则需要两个缓冲图像来处理实际重绘和操作线程之间的竞争条件。

请注意,当使用布局管理器将该组件拉伸超出其首选大小时,此骨架不会绘制组件的整个区域。请注意,如果您通过图像上的setRGB(...)执行真正的低级像素操作,或者您直接直接访问底层DataBuffer,则缓冲图像方法通常只有意义。如果你可以使用Graphics2D的方法完成所有的操作,你可以使用提供的图形(它实际上是一个Graphics2D并且可以简单地被转换)完成paintComponent方法中的所有东西。