Java:如何在Swing中进行双缓冲?

问题描述:

编辑两个

为了防止讽刺评论和单行答案错过了重点: IFF 它是就像调用 setDoubleBuffered(true)一样简单,那么如何访问当前的离线缓冲区以便我可以开始搞乱BufferedImage的底层像素数据缓冲区呢?

To prevent snarky comments and one-line answers missing the point: IFF it is as simple as calling setDoubleBuffered(true), then how do I get access to the current offline buffer so that I can start messing with the BufferedImage's underlying pixel databuffer?

我花时间写了一段正在运行的代码(看起来也很有趣)所以我真的很感激答案实际回答(多么令人震惊;)我的问题并解释这是什么/如何工作而不是one-liners and snarky comments;)

I took the time to write a running piece of code (which looks kinda fun too) so I'd really appreciate answers actually answering (what a shock ;) my question and explaining what/how this is working instead of one-liners and snarky comments ;)

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

Here's a working piece of code that bounces a square across a JFrame. I'd like to know about the various ways that can be used to transform this piece of code so that it uses double-buffering.

请注意我清除的方式屏幕和重绘方块不是最有效的,但这不是这个问题的关键(在某种程度上,为了这个例子它更好,它有点慢)。

Note that the way I clear the screen and redraw the square ain't the most efficient but this is really not what this question is about (in a way, it's better for the sake of this example that it is somewhat slow).

基本上,我需要不断修改BufferedImage中的大量像素(以便有某种动画),我不希望看到由于屏幕上的单缓冲造成的视觉伪像。

Basically, I need to constantly modify a lot pixels in a BufferedImage (as to have some kind of animation) and I don't want to see the visual artifacts due to single-buffering on screen.

我有一个JLabel,其Icon是包含BufferedImage的ImageIcon。我想修改那个BufferedImage。

I've got a JLabel whose Icon is an ImageIcon wrapping a BufferedImage. I want to modify that BufferedImage.

必须做什么才能使它成为双缓冲?

What has to be done so that this becomes double-buffered?

据我所知,在我将使用image 2时,将会以某种方式显示image 1。但是,一旦我完成了image 2的绘制,我如何快速替换image 1 image 2

I understand that somehow "image 1" will be shown while I'll be drawing on "image 2". But then once I'm done drawing on "image 2", how do I "quickly" replace "image 1" by "image 2"?

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

Is this something I should be doing manually, like, say, by swapping the JLabel's ImageIcon myself?

我是否应该总是在同一个BufferedImage中绘图,然后在JLabel的ImageIcon的BufferedImage中快速'blit'显示BufferedImage的像素? (我猜不是,我不知道我怎么能与显示器的垂直空白线[或平板屏幕中的等效物同步:我的意思是,'同步'而不会干扰显示器本身刷新它的时刻像素,以防止剪切])。

Should I be always drawing in the same BufferedImage then do a fast 'blit' of that BufferedImage's pixels in the JLabel's ImageIcon's BufferedImage? (I guess no and I don't see how I could "synch" this with the monitor's "vertical blank line" [or equivalent in flat-screen: I mean, to 'synch' without interfering with the moment the monitor itselfs refreshes its pixels, as to prevent shearing]).

重绘订单怎么样?我想我自己触发这些吗?哪个/什么时候应该调用 repaint()或其他什么?

What about the "repaint" orders? Am I suppose to trigger these myself? Which/when exactly should I call repaint() or something else?

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

The most important requirement is that I should be modifying pixels directly in the images's pixel 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应用程序。

This is not for a full-screen Java application, but a regular Java application, running in its own (somewhat small) window.

----针对每像素设置进行编辑----

---- Edited to address per pixel setting ----

项目打击可以解决双缓冲问题,但是如何将像素放入 BufferedImage 也存在问题。

The item blow addresses double buffering, but there's also an issue on how to get pixels into a BufferedImage.

如果你打电话

WriteableRaster raster = bi.getRaster()

BufferedImage 它将返回 WriteableRaster 。从那里你可以使用

on the BufferedImage it will return a WriteableRaster. From there you can use

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

请注意,您可能希望优化代码,以便不为每个渲染实际创建新数组。此外,您可能希望优化数组清除代码以不使用for循环。

Note that you would probably want to optimize the code to not actually create a new array for each rendering. In addition, you would probably want to optimized the array clearing code to not use a for loop.

Arrays.fill(pixels, 0xFFFFFFFF);

可能会优于你的循环,将背景设置为白色。

would probably outperform your loop setting the background to white.

----在响应后编辑----

---- Edited after response ----

关键在于JFrame的原始设置和运行渲染循环内部。

The key is in your original setup of the JFrame and inside the run rendering loop.

首先,你需要告诉SWING在任何时候停止栅格化;因为,当你完成绘制到想要完全换出的缓冲图像时,你会告诉它。用JFrame执行此操作

First you need to tell SWING to stop Rasterizing whenever it wants to; because, you'll be telling it when you're done drawing to the buffered image you want to swap out in full. Do this with JFrame's

setIgnoreRepaint(true);

然后你会想要创建一个缓冲策略。基本上它指定你想要使用多少个缓冲区

Then you'll want to create a buffer strategy. Basically it specifies how many buffers you want to use

createBufferStrategy(2);

既然您尝试创建缓冲策略,则需要获取 BufferStrategy 对象,因为稍后你需要它来切换缓冲区。

Now that you tried to create the buffer strategy, you need to grab the BufferStrategy object as you will need it later to switch buffers.

final BufferStrategy bufferStrategy = getBufferStrategy();

线程内修改 run()循环包含:

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

从bufferStrategy抓取的图形将是屏幕外的图形对象,在创建三重缓冲时,它将以循环方式成为下一个屏幕外图形对象。

The graphics grabbed from the bufferStrategy will be the off-screen Graphics object, when creating triple buffering, it will be the "next" off-screen Graphics object in a round-robin fashion.

图像和图形上下文在收容方案中没有关联,你告诉Swing你自己做绘图,所以你必须手动绘制图像。这并不总是坏事,因为您可以在完全绘制图像时(而不是之前)指定缓冲区翻转。

The image and the Graphics context are not related in a containment scenario, and you told Swing you'd do the drawing yourself, so you have to draw the image manually. This is not always a bad thing, as you can specify the buffer flipping when the image is fully drawn (and not before).

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

Disposing of the graphics object is just a good idea as it helps in garbage collection. Showing the bufferStrategy will flip buffers.

虽然在上面的代码中某处可能存在错误,但这应该是让你90%的方式。祝你好运!

While there might have been a misstep somewhere in the above code, this should get you 90% of the way there. Good luck!

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

---- Original post follows ----

参考可能看起来很愚蠢这是一个关于javase教程的问题,但是你看过 BufferStrategy BufferCapatbilites

It might seem silly to refer such a question to a javase tutorial, but have you looked into BufferStrategy and BufferCapatbilites?

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

The main issue I think you are encountering is that you are fooled by the name of the Image. A BufferedImage has nothing to do with double buffering, it has to do with "buffering the data (typically from disk) in memory." As such, you will need two BufferedImages if you wish to have a "double buffered image"; as it is unwise to alter pixels in image which is being shown (it might cause repainting issues).

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

In your rendering code, you grab the graphics object. If you set up double buffering according to the tutorial above, this means you will grab (by default) the off-screen Graphics object, and all drawing will be off-screen. Then you draw your image (the right one of course) to the off-screen object. Finally, you tell the strategy to show() the buffer, and it will do the replacement of the Graphics context for you.