Java: как сделать двойную буферизацию в Swing?

ИЗМЕНИТЬ ДВА

Чтобы предотвратить snarky комментарии и однострочные ответы не хватало точки: IFF это так же просто, как вызов setDoubleBuffered (true) , то как мне получить доступ к текущему автономному буфелю, чтобы я мог начать возиться с базовым databuffer пиксельных данных BufferedImage?

Я потратил время, чтобы написать бегущую часть кода (которая тоже выглядит весело), ​​поэтому я действительно ценю ответы, на которые я отвечаю (что шокирует), мой вопрос и объяснение того, что / как это работает вместо однострочных и snarky Комментарии 😉

Вот fragment кода, который отскакивает от квадрата через JFrame. Я хотел бы узнать о различных способах, которые можно использовать для преобразования этого fragmentа кода, чтобы он использовал двойную буферизацию.

Обратите внимание, что способ, которым я очищаю экран и перерисовывать квадрат, не самый эффективный, но на самом деле это не то, о чем этот вопрос (в некотором смысле, для этого примера лучше, что он несколько медленный).

В принципе, мне нужно постоянно модифицировать много пикселей в BufferedImage (чтобы иметь какую-то анимацию), и я не хочу видеть визуальные артефакты из-за однобуферизации на экране.

У меня есть JLabel, чей значок – это ImageIcon, обертывающий BufferedImage. Я хочу изменить этот BufferedImage.

Что нужно сделать, чтобы это стало двойным буфером?

Я понимаю, что покажется «образ 1» , пока я рисую «образ 2» . Но как только я закончил рисовать «образ 2» , как мне «быстро» заменить «изображение 1» на «изображение 2» ?

Это что-то, что я должен делать вручную, например, путем замены самого ImageIcon JLabel?

Должен ли я всегда рисовать в том же BufferedImage, а затем быстро «блистать» из этих пикселей BufferedImage в BufferedImage ImageIcon от JLabel? (Я думаю, нет, и я не вижу, как я могу «синхронизировать» это с «вертикальной пустой линией» монитора [или эквивалентом в плоском экране: я имею в виду, чтобы «синхронизировать», не мешая моменту, когда сам монитор обновляет его пикселей, чтобы предотвратить сдвиг]).

Как насчет «перекрасить» заказы? Предполагаю ли я сам их инициировать? Какой / когда именно я должен переписать () или что-то еще?

Наиболее важным требованием является то, что я должен изменять пиксели непосредственно в 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 = 0 && x + xs + r = 0 && y + ys + r < bi1.getHeight()) ) { ys = -ys; } x += xs; y += ys; } } 

РЕДАКТИРОВАТЬ

Это не для полноэкранного Java-приложения, а для обычного Java-приложения, работающего в собственном (несколько маленьком) окне.

—- Отредактировано для адреса на настройку пикселя —-

Элемент blow адресован двойной буферизации, но есть также проблема с тем, как получить пиксели в BufferedImage .

Если вы позвоните

 WriteableRaster raster = bi.getRaster() 

на BufferedImage он вернет 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, чтобы остановить растеризацию, когда захочет; потому что, вы будете говорить об этом, когда закончите рисовать на буферизованное изображение, которое вы хотите полностью заменить. Сделайте это с помощью JFrame’s

 setIgnoreRepaint(true); 

Затем вы захотите создать страtagsю буфера. В основном это указывает, сколько буферов вы хотите использовать

 createBufferStrategy(2); 

Теперь, когда вы пытались создать страtagsю буфера, вам нужно захватить объект 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 объект в циклическом режиме.

Контекст изображения и графики не связаны в сценарии сдерживания, и вы сказали Swing, что вы сами сделаете чертеж, поэтому вам нужно нарисовать изображение вручную. Это не всегда плохо, поскольку вы можете указать сброс буфера, когда изображение полностью вычерчено (а не раньше).

Утилизация графического объекта – это просто хорошая идея, так как это помогает в сборке мусора. Отображение bufferStrategy будет переворачивать буферы.

Хотя в приведенном выше коде может быть что-то неправильное, это должно дать вам 90% пути. Удачи!

—- Оригинальное сообщение следует —-

Может показаться глупым ссылаться на такой вопрос на javase-учебник, но вы заглянули в BufferStrategy и BufferCapatbilites ?

Основная проблема, о которой я думаю, вы сталкиваетесь с тем, что вас обманули именем Image. BufferedImage имеет ничего общего с двойной буферизацией, это связано с «буферизацией данных (обычно с диска) в памяти». Таким образом, вам понадобится два BufferedImages, если вы хотите иметь «двойное буферное изображение»; так как неразумно изменять пиксели в изображении, который отображается (это может вызвать проблемы с перерисовкой).

В вашем коде рендеринга вы захватываете графический объект. Если вы настроили двойную буферизацию в соответствии с приведенным выше учебным пособием, это означает, что вы будете захватывать (по умолчанию) экранный Graphics объект, и весь рисунок будет за пределами экрана. Затем вы нарисуете свое изображение (конечно, конечно) на экранный объект. Наконец, вы говорите страtagsи, чтобы show() буфер, и он заменит контекст Graphics для вас.

Обычно мы используем class Canvas, который подходит для анимации на Java. Anyhoo, вот как вы добиваетесь двойной буферизации:

 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 из streamа, за которым следует вызов «repaint» на объекте canvas. Такая же техника должна работать и на JPanel.

То, что вы хотите, в принципе невозможно в оконном режиме с Swing. Нет никакой поддержки для растровой синхронизации для переименования окон, это доступно только в полноэкранном режиме (и даже тогда может не поддерживаться всеми платформами).

Компоненты Swing по умолчанию имеют двойной буферизм, то есть они будут делать весь рендеринг промежуточного буфера, и затем буфер затем будет скопирован на экран, избегая мерцания от очистки фона, а затем рисует поверх него. И это единственная страtagsя, которая разумно поддерживается на всех базовых платформах. Он избегает только перерисовки мерцания, но не визуального разрыва от движущихся графических элементов.

Достаточно простой способ доступа к необработанным пикселам области, полностью находящейся под вашим контролем, заключается в расширении пользовательского компонента из 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 () на нем. Если вы выполняете манипуляции с пикселями из streamа, отличного от EDT, вам нужны два буферизованных изображения, чтобы справиться с условиями гонки между фактическим перерисовкой и streamом манипуляции.

Обратите внимание, что этот скелет не будет рисовать всю область компонента при использовании с менеджером макета, который растягивает компонент за свой предпочтительный размер.

Также обратите внимание, что подход с буферизованным изображением в основном имеет смысл, если вы выполняете реальную манипуляцию пикселями низкого уровня с помощью setRGB (…) на изображении или напрямую обращаетесь непосредственно к базовому DataBuffer. Если вы можете делать все манипуляции с помощью методов Graphics2D, вы можете сделать все это в методе paintComponent, используя предоставленную графику (которая на самом деле является Graphics2D и может быть просто литой).

Ниже приведена вариация, в которой весь рисунок происходит в streamе отправки событий .

Приложение:

В принципе, мне нужно постоянно изменять много пикселей в BufferedImage

Эта кинетическая модель иллюстрирует несколько подходов к анимации пикселей.

 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; } } 
  • Интеграция JavaFX 2.0 WebView в приложение Swing Java SE 6
  • Как нарисовать дерево, представляющее график подключенных узлов?
  • Обнаружение ввода нажмите в JTextField
  • Java GIF анимация не перерисовывается правильно
  • Swing - Обновить ярлык
  • Эффект безопасного рабочего режима для Java-приложения
  • Как заменить AWQ EventQueue собственной реализацией
  • Прогресс Bar Java
  • Java - глобальный диалог многоразового загрузки
  • Эффективный размер экрана с Java
  • Как центрировать ячейки в JTable
  • Давайте будем гением компьютера.