Syn*_*r0r 17 java swing doublebuffered
编辑二
为了防止讽刺评论和单行答案错过了重点:IFF它就像调用setDoubleBuffered(true)一样简单,那么如何才能访问当前的离线缓冲区以便我可以开始搞乱BufferedImage的底层像素数据缓冲区呢?
我花时间写了一段正在运行的代码(看起来也很有趣)所以我真的很感激答案实际上回答(多么令人震惊;)我的问题并解释这是什么/如何工作而不是单行和snarky评论 ;)
这是一段可以在JFrame上反弹的代码.我想知道可以用来转换这段代码的各种方法,以便它使用双缓冲.
请注意,我清除屏幕和重绘方块的方式并不是最有效的,但这实际上不是这个问题的关键(在某种程度上,为了这个例子它更好,它有点慢).
基本上,我需要不断修改BufferedImage中的很多像素(因为有某种动画),我不希望看到由于屏幕上的单缓冲而产生的视觉伪像.
我有一个JLabel,其Icon是一个包装BufferedImage的ImageIcon.我想修改那个BufferedImage.
必须做什么才能使其成为双缓冲?
据我所知,当我在"图像2"上画画时,会以某种方式显示"图像1 ".但是,那么一旦我做借鉴"图像2",我该如何"迅速"替换"图像1"的"图像2"?
这是我应该手动做的事情,比如说,自己交换JLabel的ImageIcon吗?
我应该总是绘制相同的BufferedImage,然后在JLabel的ImageIcon的BufferedImage中快速'blit'的BufferedImage像素?(我猜不是,我不知道我怎么能用显示器的"垂直空白线"[或平板屏幕中的等效物]"同步"这个:我的意思是'同步'而不会干扰显示器本身刷新它的时刻像素,以防止剪切]).
"重漆"订单怎么样?我想我自己触发这些吗?哪个/什么时候应该调用repaint()或其他什么?
最重要的要求是我应该直接在图像的像素数据缓冲区中修改像素.
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;
}
}
Run Code Online (Sandbox Code Playgroud)
编辑
这不是一个全屏Java应用程序,而是一个常规Java应用程序,它运行在自己的(有点小)窗口中.
Edw*_*uck 13
----编辑解决每像素设置----
项目打击解决了双缓冲问题,但是如何将像素放入a中也存在问题BufferedImage
.
如果你打电话
WriteableRaster raster = bi.getRaster()
Run Code Online (Sandbox Code Playgroud)
在BufferedImage
它将返回一个WriteableRaster
.从那里你可以使用
int[] pixels = new int[WIDTH*HEIGHT];
// code to set array elements here
raster.setPixel(0, 0, pixels);
Run Code Online (Sandbox Code Playgroud)
请注意,您可能希望优化代码,以便不为每个渲染实际创建新数组.此外,您可能希望优化阵列清除代码以不使用for循环.
Arrays.fill(pixels, 0xFFFFFFFF);
Run Code Online (Sandbox Code Playgroud)
可能会胜过你的循环,将背景设置为白色.
----回复后编辑----
关键在于JFrame的原始设置和运行渲染循环内部.
首先,你需要告诉SWING在任何时候停止栅格化; 因为,当你完成绘制到想要完全换出的缓冲图像时,你会告诉它.用JFrame做到这一点
setIgnoreRepaint(true);
Run Code Online (Sandbox Code Playgroud)
然后你会想要创建一个缓冲策略.基本上它指定了您要使用的缓冲区数
createBufferStrategy(2);
Run Code Online (Sandbox Code Playgroud)
现在您尝试创建缓冲区策略,您需要获取BufferStrategy
对象,因为稍后需要它来切换缓冲区.
final BufferStrategy bufferStrategy = getBufferStrategy();
Run Code Online (Sandbox Code Playgroud)
在你Thread
修改run()
循环内部包含:
...
move();
drawSqure(bi1);
Graphics g = bufferStrategy.getDrawGraphics();
g.drawImage(bi1, 0, 0, null);
g.dispose();
bufferStrategy.show();
...
Run Code Online (Sandbox Code Playgroud)
从bufferStrategy抓取的图形将成为屏幕外Graphics
对象,在创建三重缓冲时,它将以Graphics
循环方式成为"下一个"屏幕外对象.
图像和图形上下文在包含场景中没有关联,你告诉Swing你自己做绘图,所以你必须手动绘制图像.这并不总是坏事,因为您可以在完全绘制图像时(而不是之前)指定缓冲区翻转.
处理图形对象只是一个好主意,因为它有助于垃圾收集.显示bufferStrategy
将翻转缓冲区.
虽然在上面的代码中某处可能存在错误,但这应该可以让你获得90%的失误.祝好运!
----原帖如下----
这似乎傻是指这样一个问题的JavaSE的教程,但你看着BufferStrategy
和BufferCapatbilites
?
我认为你遇到的主要问题是你被图像的名字所欺骗.A BufferedImage
与双缓冲无关,它与"缓冲内存中的数据(通常来自磁盘)"有关.因此,如果您希望拥有"双缓冲图像",则需要两个BufferedImages; 因为改变正在显示的图像中的像素是不明智的(这可能会导致重新绘制问题).
在渲染代码中,您将获取图形对象.如果您根据上面的教程设置了双缓冲,这意味着您将(默认情况下)抓取屏幕外Graphics
对象,并且所有绘图都将在屏幕外.然后,您将图像(当然是正确的)绘制到屏幕外对象.最后,您将策略告诉show()
缓冲区,它将为您替换Graphics上下文.
一般我们使用Java中适合动画的Canvas类。无论如何,以下是实现双缓冲的方法:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
现在,您可以从线程更新 x_pos 和 y_pos,然后对画布对象进行“重绘”调用。相同的技术也应该适用于 JPanel。