Подтвердить что ты не робот

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

EDIT TWO​​strong >

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

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

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

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

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

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

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

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

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

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

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

Наиболее важным требованием является то, что я должен изменять пиксели непосредственно в 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, работающего в собственном (несколько маленьком) окне.

4b9b3361

Ответ 1

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

Элемент 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

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 и BufferCapatbilites?

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

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

Ответ 2

Обычно мы используем класс 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 из потока, за которым следует вызов "repaint" на объект canvas. Тот же метод должен работать и на JPanel.

Ответ 3

Здесь вариация, в которой весь рисунок имеет место в потоке отправки событий.

Приложение:

В принципе, мне нужно постоянно изменять много пикселей в 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;
    }
}

Ответ 4

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