2

カスタムpaintComponent()実装を備えたJPanelサブクラスがあります。50fpsで更新されています。通常、サイズは500x300ピクセルの範囲です。ちらつきが見られ(それほど悪くはありませんが目立ちます)、Swing / EDTが(おそらく)冗長なペイントをスキップしていることを示すデバッグコードを挿入しました。これは、EDTがpaintComponent()を常に終了するのに十分な時間を与えていないか、EDTで時間がかかりすぎているためだと思います。

私の考えでは、現在paintComponent()を実装しているコード(それほど複雑ではありませんが、完全に簡単ではありません)をリファクタリングして、独自のスレッド(または少なくともEDTではない)で実行され、 ImageBuffer。次に、カスタムJPanelにpaintComponentを実装し、ImageBufferから画面に描画(レンダリング)します(実際には、ソリューションの調査により、Swingが(デフォルトで)ダブルバッファーになっているという情報が得られたため、Swingコンポーネントの背後にあるバッファーに描画します。それについては完全には明確ではありません)。ImageBufferからJPanelへのレンダリングが、ImageBufferを構築する私の実装よりも速いことが事実である場合、私は正しい方向に進んでいます。

これは私にとって適切な設計の方向性ですか?

アップデート

以下の回答で説明されているように、実装を変更しました。

1)BufferedImageを作成します

BufferedImage myBufferedImage = new BufferedImage(mySize.width,mySize.height,BufferedImage.TYPE_INT_ARGB)

2)何を描画するかを決定するために、処理の実行専用のスレッドを作成します。

3)以前にpaintComponent()にあったコードを、専用のスレッドによって実行される別のメソッドに移動します。このメソッドの最後に、repaint();を呼び出します。

4)単に呼び出す新しいpaintComponent()を作成しますg.drawImage(myBufferedImage,0,0,null);

5)以前はrepaint()を呼び出していた場所で、myThreadをトリガーしてmyBufferedImageへの描画を実行します。

予想通り、これは災害でした。ちらつきや動きの鈍さ、部分的なペイントなどがさらに悪化しました。これは、myBufferedImageの読み取り/書き込みの競合が原因であると考えられます(以下で説明します)。そこで、(専用の描画スレッドで)書き込み中にmyBufferedImageをロックしてロックし、graphs2D.drawImage()を呼び出す前にpaintComponent()でそのロックを取得するのを待ちます。ちらつきや部分的なペイントはなくなりますが、paintComponent(したがってEDT)で描画のすべての計算を行ったときよりも、パフォーマンスは良くなりません(おそらくさらに悪くなります)。

これは私がこの時点で困惑しています。

4

3 に答える 3

3

コンポーネント全体を更新していない場合 (つまり、小さな領域のみが変更されている場合)、JComponent#repaint(Rectangle r)変更された領域を示すことができます。これにより、(潜在的に) はるかに小さな領域を更新する再描画サイクルが発生します。

少し前に「アニメーションシーケンス」ライブラリを生成して、一連の画像を取得し、各レイヤーの「速度」が与えられると、それらを右から左に転置します。

シーケンス全体が 10 秒間循環し、速度 1 では完了するまでに 10 秒かかります。各層は異なる速度で移動しています。

元の画像は 1024x256 で、シーケンスは 5 つのアニメーション レイヤーと 2 つの静的レイヤーで考案されました...

これが私の PC と Mac でどのようにスムーズに再生されるかをお見せしたいと思います。

ここに画像の説明を入力

私が克服しなければならなかった唯一の重要な問題は、画像がスクリーン デバイスのカラー モデルと互換性があることを確認することでした。

更新しました

BufferedImageこれらは、特にアニメーション用に をロードまたは作成するときに使用するユーティリティ クラスです。カラーモデルが画面で使用されているものと同じであることを確認してください。これにより、更新/再描画が高速になります

public static BufferedImage loadCompatibleImage(URL resource) {

    BufferedImage image = null;

    try {
        image = ImageIO.read(resource);
    } catch (IOException ex) {
    }

    return image == null ? null : toCompatibleImage(image);

}

public static BufferedImage toCompatibleImage(BufferedImage image) {

    if (image.getColorModel().equals(getGraphicsConfiguration().getColorModel())) {

        return image;

    }

    BufferedImage compatibleImage =
            getGraphicsConfiguration().createCompatibleImage(
            image.getWidth(), image.getHeight(),
            image.getTransparency());

    Graphics g = compatibleImage.getGraphics();
    g.drawImage(image, 0, 0, null);
    g.dispose();

    return compatibleImage;

}


public static GraphicsConfiguration getGraphicsConfiguration() {

    return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();

}

// Check out java.awt.Transparency for valid values
public static BufferedImage createCompatibleImage(int width, int height, int transparency) {

    BufferedImage image = getGraphicsConfiguration().createCompatibleImage(width, height, transparency);
    image.coerceData(true);
    return image;

}
于 2012-11-16T21:33:54.230 に答える
1

これは、ダブルバッファリングに関する情報についてあなたが探しているものだと思います:

http://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html

基礎となるバッファにアクセスできない場合は、 setDoubleBuffered(false) でダブルバッファリングをオフにすることができますが、アクセスできるかどうかは完全にはわかりません。

別のスレッドから安全に画像を描画できるとは思いません。なぜなら、EDT が再描画時に同じ画像を読み取っている間にスレッドが画像に書き込むことになるからです。それらの間でイメージを共有すると、同期する必要があるマルチスレッドの問題が発生します。同期すると、パフォーマンスはあまり良くなりません。フレームごとに新しい画像をインスタンス化すると、メモリが急増し、GC が取得します。10 フレームをインスタンス化して、書き込みを読み取りなどから遠ざけることができるかもしれませんが、いずれにせよ、パフォーマンスを向上させて正確にするのは非常に困難です。

私の提案は、EDT からすべての描画を行い、ImageBuffer 共有を含まない別のスレッドで計算 (レンダリング) を行う方法を見つけることです。

フルスクリーンで使用している間に更新します。そこにある提案はウィンドウ モードにも適用されます。これを参照してくださいhttp://docs.oracle.com/javase/tutorial/extra/fullscreen/rendering.html

于 2012-11-17T17:11:18.727 に答える
0

私はスムーズにペイントしようとして同様の問題を抱えています。

これを実行してみて、どれだけ滑らかかを確認してください (私にとっては滑らかです)。

プロファイラーは、ほとんどの場合、ペイント コンポーネントにあると言います。興味深いことにドロー画像は言及されていません。

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
class P extends JPanel {
    void init(Dimension d) {
        GraphicsConfiguration gc=getGraphicsConfiguration();
        bi=gc.createCompatibleImage(d.width,d.height);
    }
    @Override public void paintComponent(Graphics g) {
        //super.paintComponent(g);
        if(bi!=null)
            g.drawImage(bi,0,0,null);
    }
    BufferedImage bi;
}
public class So13424311 {
    So13424311() {
        p=new P();
    }
    void createAndShowGUI() {
        Frame f=new JFrame("so13424311");
        // f.setUndecorated(true);
        f.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        f.add(p);
        p.init(d);
        p.setSize(d);
        p.setPreferredSize(d);
        f.pack();
        // if(moveToSecondaryDisplay)
        // moveToSecondaryDisplay(f);
        f.setVisible(true);
    }
    void run() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
        Timer t=new Timer(20,new ActionListener() {
            @Override public void actionPerformed(ActionEvent e) {
                Graphics g=p.bi.getGraphics();
                Color old=g.getColor();
                g.fillRect(0,0,d.width,d.height);
                g.setColor(Color.red);
                g.fillRect(n%(d.width/2),n%(d.height/2),20,20);
                g.setColor(Color.green);
                g.fillRect(n%(d.width/2)+20,n%(d.height/2),20,20);
                g.setColor(Color.blue);
                g.fillRect(n%(d.width/2),n%(d.height/2)+20,20,20);
                g.setColor(Color.yellow);
                g.fillRect(n%(d.width/2)+20,n%(d.height/2)+20,20,20);
                g.setColor(old);
                g.dispose();
                p.repaint();
                n++;
            }
            int n;
        });
        t.start();
    }
    public static void main(String[] args) {
        new So13424311().run();
    }
    final P p;
    Dimension d=new Dimension(500,300);
}
于 2012-11-20T02:32:33.587 に答える