web-dev-qa-db-ja.com

Java:Swingでダブルバッファリングを行う方法は?

2つ編集

卑劣なコメントと1行の回答がポイントを見逃さないようにするには:[〜#〜] iff [〜#〜]setDoubleBufferedを呼び出すのと同じくらい簡単です(true)では、現在のオフラインバッファーにアクセスして、BufferedImageの基になるピクセルデータバッファーをいじり始めるにはどうすればよいですか?

実行中のコードを書くのに時間をかけたので(これもちょっと面白そうです)、私の質問に実際に答えて(なんてショックです;)、ワンライナーや卑劣なものではなく、これがどのように機能しているかを説明する答えを本当に感謝していますコメント;)

これは、JFrame全体で正方形をバウンスするコードの作業部分です。このコードをダブルバッファリングを使用するように変換するために使用できるさまざまな方法について知りたいのですが。

画面をクリアして正方形を再描画する方法は最も効率的ではないことに注意してくださいが、これは実際にはこの質問の内容ではありません(ある意味、この例では多少遅い方が良いです)。

基本的に、私はBufferedImageの多くのピクセルを絶えず変更する必要があり(ある種のアニメーションを作成するため)、画面上の単一のバッファリングによる視覚的なアーティファクトを見たくありません。

私は、アイコンがBufferedImageをラップするImageIconであるJLabelを持っています。そのBufferedImageを変更したいと思います。

これがダブルバッファになるために何をする必要がありますか?

"画像2"を描画しているときに、どういうわけか "画像1"が表示されることを理解しています。しかし、 "image 2"の描画が完了したら、 "image 1"を「すばやく」置き換えるにはどうすればよいですか。 "画像2"

これは、たとえばJLabelのImageIconを自分で交換するなど、手動で行う必要があることですか?

常に同じBufferedImageで描画してから、JLabelのImageIconのBufferedImageでその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;
    }

}

[〜#〜]編集[〜#〜]

これはnotフルスクリーンJavaアプリケーションですが、通常のJavaアプリケーションであり、独自の(やや小さい)窓。

17
SyntaxT3rr0r

----ピクセルごとのアドレス設定に編集----

アイテムブローはダブルバッファリングに対処しますが、ピクセルを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の元の設定と、実行レンダリングループ内です。

まず、必要なときにいつでもRasterizingを停止するように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オブジェクトになります。

封じ込めシナリオでは、画像とグラフィックスコンテキストは関連しておらず、Swingに自分で描画するように指示したため、画像を手動で描画する必要があります。これは必ずしも悪いことではありません。画像が完全に描画されたときに(前ではなく)バッファフリップを指定できるからです。

グラフィックオブジェクトを破棄することは、ガベージコレクションに役立つため、良い考えです。 bufferStrategyを表示すると、バッファが反転します。

上記のコードのどこかにミスステップがあった可能性がありますが、これで90%の道のりが得られるはずです。幸運を!

----元の投稿が続きます----

そのような質問をjavaseチュートリアルに参照するのはばかげているように見えるかもしれませんが、 BufferStrategy and BufferCapatbilites を調べましたか?

あなたが直面している主な問題は、あなたがイメージの名前にだまされているということです。 BufferedImageは、ダブルバッファリングとは関係がなく、「メモリ内の(通常はディスクからの)データのバッファリング」と関係があります。そのため、「二重バッファ画像」が必要な場合は、2つのBufferedImageが必要になります。表示されている画像のピクセルを変更することは賢明ではないためです(再描画の問題が発生する可能性があります)。

レンダリングコードでは、グラフィックスオブジェクトを取得します。上記のチュートリアルに従ってダブルバッファリングを設定した場合、これは(デフォルトで)オフスクリーンのGraphicsオブジェクトを取得し、すべての描画がオフスクリーンになることを意味します。次に、画像(もちろん正しい画像)を画面外のオブジェクトに描画します。最後に、戦略にバッファをshow()するように指示すると、グラフィックスコンテキストの置き換えが行われます。

13
Edwin Buck

これは、すべての描画が イベントディスパッチスレッド で行われるバリエーションです。

補遺:

基本的に、私は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;
    }
}
3
trashgod

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以外のスレッドからピクセル操作を行う場合、実際の再描画と操作スレッドの間の競合状態に対処するために、2つのバッファリングされたイメージが必要です。

このスケルトンは、コンポーネントを適切なサイズを超えて拡大するレイアウトマネージャーと一緒に使用した場合、コンポーネントの領域全体をペイントしないことに注意してください。

また、バッファリングされた画像のアプローチは、画像に対してsetRGB(...)を介して実際の低レベルのピクセル操作を行う場合、または基になるDataBufferに直接アクセスする場合にのみ意味があります。 Graphics2Dのメソッドを使用してすべての操作を実行できる場合は、提供されているグラフィック(実際にはGraphics2Dであり、単純にキャストできます)を使用してpaintComponentメソッドのすべての操作を実行できます。

3
Durandal

通常、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);

    }
}

これで、スレッドからx_posとy_posを更新してから、canvasオブジェクトで「repaint」を呼び出すことができます。同じ手法がJPanelでも機能するはずです。

3
Usman Saleem