GUIサーバ高速化/05.部分描画実装(libbaygui)/実装/AWT/05


Top / GUIサーバ高速化 / 05.部分描画実装(libbaygui) / 実装 / AWT / 05

ダブルバッファ

描画の調査のしめくくりにダブルバッファをやってみます。

import java.awt.*;
import java.awt.event.*;

class MyFrame extends Frame {
    public static void main(String args[]) {
        new MyFrame().setVisible(true);
    }
    
    MyFrame() {
        setSize(200, 200);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        add(new MyComponent());
    }
}

class MyComponent extends Component {
    private Image buffer;
    
    public MyComponent() {
        enableEvents(AWTEvent.MOUSE_EVENT_MASK);
        buffer = createImage(200, 200);
    }
    
    public void paint(Graphics g) {
        g.drawImage(buffer, 0, 0, this);
    }
    
    protected void processMouseEvent(MouseEvent e) {
        super.processMouseEvent(e);
        if (e.getID() == MouseEvent.MOUSE_CLICKED) {
            int x = e.getX(), y = e.getY();
            Graphics g = buffer.getGraphics();
            g.drawRect(x - 5, y - 5, 10, 10);
            g.dispose();
            repaint(x - 5, y - 5, 11, 11);
        }
    }
}

これはバッファを常時保持しておいて、 システムの描画要求には自動応答して、 書き込みは必ずバッファを通すというつもりで書いたコードです。 しかし、クリックするとぬるぽで落ちてしまいます。

MyComponentのコンストラクタでcreateImage()していますが、 これが失敗してnullが返って来てしまっています。

リファレンスには次のように書いてあります。

http://java.sun.com/j2se/1.4/ja/docs/ja/api/java/awt/Component.html

public Image createImage(int width, int height)

戻り値:
ダブルバッファリングに使用できるオフスクリーン描画イメージ。コンポーネントが表示不可能な場合は null を返す。GraphicsEnvironment.isHeadless() が true を返す場合は常に null を返す

単にちらつきを抑えるため、 以下のようにpaint()の中で完結した操作をするのであれば、 このような仕様になっているのであれば納得できます。

ImageProducer

ここで一つ上のオーバーロードされた関数に注目しました。

MemoryImageSourceというのが使えそうな感じです。 というかこれ、リニアフレームバッファと同様に扱えますね。

と、ここまで書いて、gcj/JAVAde3Dのオリジナルで使われていたことを思い出しました。

それで早速使ってみました。

buffer = createImage(new java.awt.image.MemoryImageSource(
  200, 200, new int[200 * 200], 0, 200));

するとgetGraphics()することができないという例外が発生してしまいました。

getGraphics() not valid for images created with createImage(producer)

フレームバッファに自前描画すれば何でもありなのは分かりますし、 その方がMonaでの状況に近いというのも事実ですが、 AWTとして自然な使い方ではないので別の方法を考えます。

validate()

そもそもcreateImage()の制限は、 親がピアとしての実体を持たないことに起因すると想像できます。 ピアとしての実体を持った後にcreateImage()すれば良さそうです。

実体を作るのはMFCや.NETではCreate()で行われますが、 ちょっと見た限りでは相当するものが見付かりませんでした。 ですがそういう準備が整った後でComponent#validateが呼ばれるようです。 というわけでそれをコンストラクタの代わりに使ってみました。 そうなるとコンストラクタがいらなくなるので無名クラスにできます。

import java.awt.*;
import java.awt.event.*;

class MyFrame extends Frame {
    public static void main(String args[]) {
        new MyFrame().setVisible(true);
    }

    MyFrame() {
        setSize(200, 200);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        add(new Component() {
            private Image buffer;

            public void validate() {
                super.validate();
                enableEvents(AWTEvent.MOUSE_EVENT_MASK);
                buffer = createImage(200, 200);
            }

            public void paint(Graphics g) {
                g.drawImage(buffer, 0, 0, this);
            }

            protected void processMouseEvent(MouseEvent e) {
                super.processMouseEvent(e);
                if (e.getID() == MouseEvent.MOUSE_CLICKED) {
                    int x = e.getX(), y = e.getY();
                    Graphics g = buffer.getGraphics();
                    g.drawRect(x - 5, y - 5, 10, 10);
                    g.dispose();
                    repaint(x - 5, y - 5, 11, 11);
                }
            }
        });
    }
}

うまくいきました。

しかし、ちょっと使ってみて気付きましたが、 リサイズする度にvalidate()が呼ばれるようです。 とりあえず無視するようにしてみました。

if (buffer == null) buffer = createImage(200, 200);

サイズ変更にバッファが追随しませんが、 それは承知の上で無視しているので問題ありません。

ところでここで注目するのは描画の作法です。 こういう部分を見抜かないとコードを読む意味がないというものです。

Graphics g = buffer.getGraphics();
g.drawRect(x - 5, y - 5, 10, 10);
g.dispose();
repaint(x - 5, y - 5, 11, 11);

部分描画

paint()で全描画しているのが気になるので部分描画してみました。

           public void paint(Graphics g) {
               Rectangle r = g.getClipBounds();
               int x1 = r.x, x2 = x1 + r.width, y1 = r.y, y2 = y1 + r.height;
               g.drawImage(buffer, x1, y1, x2, y2, x1, y1, x2, y2, this);
           }

でもよく考えたらGraphicsの段階でクリッピングされているので、 drawImage()の内部でマスクして最適化されていれば、 いちいちこんなコードを書く必要がないということに気付きました。

ところでdrawImage()はオーバーロードが多いのですが、NetBeansの候補の出し方が非常に分かりやすくて感心しました。VS.NETより使いやすいです。

ついでに、NetBeansの補完はcase sensitiveなのがちょっと気になりました。drawiの時点で候補が消えてしまうというようなことです。 そういえばVBを使っていたときは定義はきちんとCamelCaseにしておいて、 入力は全部小文字でして確定されるときに整形されるのが小気味良かったです。

それはともかく、これでAWTの調査は終わりにします。

最後に

仕事で関わったりしたこともなかったため、 今までJavaで開発する機会がありませんでした。 コンパイルや実行の作法などを覚えずにいきなりコードが書けるという点で、 NetBeansをバンドルしたJDKというのはとても便利だと思いました。

そういえば昔購入したものの一度しか使わなかったVisual Age for Javaも、 セットアップするだけですぐに開発できたのを思い出しました。 Visual Ageはカラムブラウザとか接続とか、 今でも色あせない面白いアイデアが一杯ありました。 当時はオブジェクト指向が良く分からなかったのと、 Javaの重さに耐え切れずに使い込むことはありませんでした。 まあそれは言い訳に過ぎず、 昔から手軽に開発環境を構築することは出来ていたのに、 それを使わなかったのは私の能力不足ということでしょう。

コメント

コメントはありません。 コメント/GUIサーバ高速化/05.部分描画実装(libbaygui)/実装/AWT/05?

お名前:

MENU

now: 2

リンク


最新の20件
2018-10-07 2018-09-20 2018-09-03 2018-05-09 2017-09-29 2017-01-10 2016-12-11 2016-10-04 2016-08-14 2016-05-29 2015-12-28 2013-02-25 2013-02-21 2013-02-20 2013-02-12 2013-02-11 2013-02-10
最新の20件
2010-02-01 2010-01-31 2010-01-30 2010-01-29 2010-01-16

Counter: 4015, today: 1, yesterday: 0

リロード   新規 編集 凍結 差分 添付 複製 改名   トップ 一覧 検索 最終更新 バックアップ   ヘルプ   最終更新のRSS

Last-modified: 2008-03-28 (金) 15:47:54 (3857d);  Modified by mona
PukiWiki 1.4.6 Copyright © 2001-2005 PukiWiki Developers Team. License is GPL.
Based on "PukiWiki" 1.3 by yu-ji
Powered by PHP 5.2.17
HTML convert time to 0.041 sec.