2010-12-13 4 views
17

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

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

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

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

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

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

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

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

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

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

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

Как насчет заказов «перекрасить»? Предполагаю ли я сам их инициировать? Который/когда именно я должен позвонить 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; 
    } 

} 

EDIT

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

+2

По умолчанию используется качание по умолчанию. Это не AWT. – camickr

+0

Все еще ждет, когда кто-то опубликует фактический код, который мы можем проверить, чтобы увидеть, стоит ли двойная буферизация. – camickr

+1

Я думаю, вы неправильно поняли качели. Вы не можете получить доступ к (немодифицированному) автономному буферу. Swing пропустит буфер до вашего родителя, и ваш родитель изменит его. ... Чтобы делать то, что вы хотите, вы должны * отключить * swing doublebuffering и сделать это, скажем, BufferedImage. Если вы хотите сделать настоящий битбит, вам придется использовать «тяжеловесный» компонент в AWT. –

ответ

3

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

+0

@ Усман Салем: Привет, Сейлем, здорово видеть, что здесь помогает новичок :) При работе с Canvas я могу также получить доступ к databuffer пикселей таким же образом, как и с BufferedImage? – SyntaxT3rr0r

+0

Не следует ли вызывать перерисовку в EDT? – Riduidel

+1

Но это не нужно. Swing также позволяет анимацию. Это не двойная буферизация. Или, если это один и тот же код, можно использовать в Swing JPanel, поскольку все, что вы делаете, это рисование изображения. – camickr

11

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

Элемент удар адреса двойной буферизации, но есть также вопрос о том, как получить пиксели в 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); 

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

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 объект в круговом стиле.

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

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

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

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

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

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

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

+0

@Edwin Buck: +1 для справки, но я не использую BufferedImage, потому что это имя меня обманет: я использую их, потому что способность манипулировать пикселями прямо на изображении является обязательным требованием и единственным способом, который я знаю получить доступ к базовому databuffer пикселя изображения, чтобы использовать BufferedImage. – SyntaxT3rr0r

+0

@SpoonBender хорошо, я был немного в мысли, что вам нужно два BufferedImages, на самом деле вы можете обойтись только одним, потому что вам нужно нарисовать его для каждого буфера отдельно. Я обновил свой пост, давая подробности о том, как можно интегрировать поддержку двойной буферизации Swing в ваш пример. –

+0

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

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

Смежные вопросы