- PR -

効率の良い描画方法とは?

投稿者投稿内容
ボム
ベテラン
会議室デビュー日: 2003/07/25
投稿数: 61
投稿日時: 2003-08-08 13:24
こんにちは、さくらば様、そして再度のご意見感謝します。

さてまず、下記がスレッド部分です。
行っている事はスレッド開始2秒後に文字列の位置X値をずらし、描画をしているだけです。
スレッドが開始されるのはパネルにフォーカスが移った時で、スレッドが停止するのはパネル
からフォーカスが外れたときです。
イベント処理はフォーカスイベントのみです。上記でも書いたとおりフォーカスの有無でスレッドを走らせています。フォーカスイベントはスレッドソースの下に明記しました。
*******************************************************************************
public void run(){
 //パネルに表示される文字列の長さ
 FontMetrics fontM = this.getFontMetrics(TitleFont);
 int messageLen = fontM.stringWidth(Number);
 int movePoint = 0;
 if(messageLen >= sizePanelW) {
  try {
   threadScroll.sleep(threadSleep);
   while (threadScroll != null) {
    threadScroll.sleep(threadRun); //0.05秒間隔で動きます。
    moveTitleX -= 1; //左へずれた文字位置
    if (movePoint > (messageLen - 20)) { //全文字列がパネルに表示されたら
     posiNumberX = posiDefaultNumberX; //表示位置を初期値へ戻す
     movePoint = 0;
     repaint();
     threadScroll.sleep(threadSleep);
    } else {
     movePoint = movePoint + 1;
     repaint();
    }
   }
  } catch (InterruptedException ie) {}
 }
}
*******************************************************************************
//フォーカス取得イベント
void pnlAlbum_focusGained(FocusEvent e) {
 if(im_status != 2) {
  changeImage(1); //main_imgイメージを変更します。
  repaint();
 }
  threadOnOff(true); //スレッドを開始します。
}
//フォーカス損失イベント
void pnlAlbum_focusLost(FocusEvent e) {
 if(im_status != 2) {
  changeImage(0); //main_imgイメージを変更します。  repaint();
 }
  threadOnOff(false); //スレッドを開始します。

}
*******************************************************************************

>Graphics#clearRectとかでイメージを消去することだったと思います。
"paint()"メソッドの"else"文の下に下記のクリア文を入れた所、フォーカス取得の度に
パネルがちらついてしまいます。
offGraphics.clearRect(0,0,sizePanelW, sizePanelH);
未記入
大ベテラン
会議室デビュー日: 2003/06/28
投稿数: 219
投稿日時: 2003-08-08 16:25
こんにちは。
ちょこっと実験してみました。
引用:

さくらばさんの書き込み (2003-08-08 11:45) より:
flush は必要ないです。多分、unibon さんがいいたいのは、Graphics#clearRect
とかでイメージを消去することだったと思います。


やはりclearRectがないと崩れるようでした。(イベント競合による問題は手元で実験する
限り再現しませんでした。)
チラツキについてですが、update (Graphics g)に書き出すことで、防止できそうです。
:以下参考ソースです。
コード:
import java.awt.*;
import java.applet.Applet;
import java.awt.event.*;
import java.net.*;

public class sample5 extends Applet implements Runnable, AdjustmentListener{

	Thread thread;
	Scrollbar scrollbar;
	Image image, offImage;
	Graphics offGraphics;
	URL url;
	int posx = 240;
	int s = 1;
	boolean setInit = false;
	public void init (){
		try{
			url = new URL ("http://192.168.0.2/");
			image = getImage (url, "./Test.jpg");
		}
		catch (Exception e){
		}
		setLayout (new BorderLayout ());
		scrollbar = new Scrollbar (Scrollbar.VERTICAL, 0, 10, 0, 110);
		add ("East", scrollbar);
		scrollbar.addAdjustmentListener (this);
		thread = new Thread (this);
		thread.start ();
	}

	public void paint (Graphics g){
		update (g);
	}

	public void update (Graphics g){
		if (!setInit){
			offImage = this.createImage (200, 200);
			offGraphics = offImage.getGraphics ();
			setInit = true;
		}
		offGraphics.clearRect (0, 0, 200, 200);
		// 絵(素材)がなかったのでループして描いてます(汗)。
		for (int i = 0; i < 1000; i += 100){
			offGraphics.drawImage (image, 10, s + i, this);
		}
		offGraphics.drawString ("Hello!", posx, 100);
		g.drawImage (offImage, 0, 0, this);
	}

	public void adjustmentValueChanged (AdjustmentEvent e){
		s = scrollbar.getValue () * -10;
		repaint ();
	}

	public void run (){
		boolean j = true;
		try{
			while (j){
				Thread.sleep (50);
				--posx;
				repaint ();
				if (posx <= -40) posx = 240;
			}
		}
		catch (Exception e){
		}
	}

}


かずくん
ぬし
会議室デビュー日: 2003/01/08
投稿数: 759
お住まい・勤務地: 太陽系第三惑星
投稿日時: 2003-08-09 16:48
まず作成する機能をイメージさせてください。

 フォーカスのあるコンポーネントの文字列が常にアニメーションしていて、
 上下キーを押すとアニメーションする文字列が1つ変更し、またキーを押しつづけると、
 連続して文字列が変更する。

というものを連想すれば良いでしょうか。以下、上述のような機能であると仮定して話を進めます

この場合、連続してアニメーションさせるため、キーを押しつづけることで、おそらくKeyListener.keyTyped()が呼ばれることでしょう。しかし、1つの文字列がスクロールし終わるまではやることがないはずです。

にもかかわらず、
引用:

このペイントがされるのはスレッドによる文字の移動時と、キー入力イベント時です。



であるということは、KeyListener.keyTyped()メソッドが呼ばれるたびに再描画を行っているということでしょうか。

この際描画は無駄ではないでしょうか。animation-threadによる再描画だけで十分ではないでしょうか。

KeyListener.keyType()メソッドで行うことは、表示する文字列をqueueに入れておき、queueが空になるまでは、なにもせず、とっとと抜け出てしてやる。
queueは要素を1つしかか入れることが出来ないという制約を与えておきます。

一方、animation-threadは、現在表示中の文字列が完全に表示されたら、queueをチェックし、存在すればqueueから表示する文字列を取り出し、空であればそのまま今の文字列を、一定のタイミングで再表示するようにすれば良いのではないでしょうか。

このようにすれば、表示が崩れることを防ぐことが出来るのではないでしょうか。
ただし、はじめにイメージした機能がCasperさんの要求する機能と乖離しているようであれば、使えないですが。
さくらば
大ベテラン
会議室デビュー日: 2002/11/12
投稿数: 145
投稿日時: 2003-08-12 02:09
こんにちは、さくらばです。

引用:

Casperさんの書き込み (2003-08-08 13:24) より:

>Graphics#clearRectとかでイメージを消去することだったと思います。
"paint()"メソッドの"else"文の下に下記のクリア文を入れた所、フォーカス取得の度に
パネルがちらついてしまいます。
offGraphics.clearRect(0,0,sizePanelW, sizePanelH);




今回の場合は、描画処理がボトルネックになっているので、clearRect を入れると
なおさら描画処理が遅くなって、チラツキの原因になっているようです。

前に書いたように、offScreen に main_img を描画していることで、offScreen の
クリアと同じ効果になっていますから、ここでは clearRect は必要ありません。

さて、ボトルネックの解消ですが、かずくんさんが指摘されているように、
repaint のコールが多すぎるのだと私も思います。

フォーカス獲得を行ったらスレッドで定期的に再描画しているので、下のように
時間軸に沿って再描画されているはずです。R が再描画です。

コード:
Time    -----------------------------------------------------------
Thread     R          R          R          R          R          R



これにキー入力 (keyTyped イベント) が起きたときに repaint を行っていると

コード:
Time    -----------------------------------------------------------
Thread     R          R          R          R          R          R
KeyTyped      R R R R   R R R R R  R RRRR R   R R R



というように、repaint が大量にコールされてしまうわけです。
しかし、そんなに repaint してもアニメーション的には効果はあまりありません。
例えば、映画は 24 コマ/秒 ですから、約 40 ms ごとに再描画していることになります。
それを考えると、Casper さんのプログラムでは 50 ms ごとに再描画しているようなので、
これ以上再描画を増しても人間の目には認識できなくなってしまいます。

ということで、キー入力時には再描画しないようにすればいいと思います。

かずくんさんは要素数 1 のキューを考えてらっしゃるようですが、そこまでしなくとも
次のように書けばいいのではないでしょうか。こうすれば、他の部分は変更しなくても OK です。

コード:
public void keyTyped(KeyEvent event) {
    switch (event.getKeyCode()) {
      case KeyEvent.VK_UP:
        if(im_status != 2) {
            changeImage(1); // 適当に書いています ^^;;
	    Number = "1";
        }
        break;
      case KeyEvent.VK_DOWN:
        if(im_status != 2) {
            changeImage(0); // 適当に書いています ^^;;
	    Number = "0";
        }
        break;
    }
}


ほむら
ぬし
会議室デビュー日: 2003/02/28
投稿数: 583
お住まい・勤務地: 東京都
投稿日時: 2003-08-12 10:57
ども、ほむらです。
もしかしたら、出来もしない
とんちんかんなことなのかもしれませんが。。。
このケースって本当に処理速度の問題なのでしょうか?
------------------
>残像といいますか、前の文字や絵がパネル
>上に残ってしまい、新しく描画した絵や文字列と重なってしまうことがあります。
この部分が気になります。
この重なってしまう現象は永続的なものですか?
それとも、本当に一瞬ですか?
ここでいう永続とは次回paint()が呼び出されるまで残るかどうかということです。

後者であれば処理速度の問題とは思いますが前者ならば
処理手順に問題があるように思えます。
処理手順に問題があったと仮定する場合
こんなことは出来ませんか?(すべてオフスクリーンに対してです)
1.キー入力時に新しい画像を描く前にいったんクリアする。(透明色への対応)
  offGraphics.clearRect(0,0,sizePanelW, sizePanelH);  でいいのかな?
2.paint()での更新時に画像全体でなく文字の書かれる部分のみ画像を描く
  (posiNumberY+文字高さ程度と画像全体の幅)

# 通常ならばオフスクリーンを使用すれば全体的にちらつくことはあっても
# 崩れるなどの症状はでないはず。。。

# それっぽい関数を見つけたので追加(環境ないので未実験^^;;;;;)
コード:

drawImage(
main_img,
/* destonation rectangle */
0,
posiNumberY,
sizePanelW,
posiNumberY + StringHeight,
/* source rectangle */
0,
posiNumberY,
sizePanelW,
posiNumberY + StringHeght,
this
);


# MS-DOSの時代をなんとなくおもいだす今日この頃


[ メッセージ編集済み 編集者: ほむら 編集日時 2003-08-12 11:12 ]
未記入
大ベテラン
会議室デビュー日: 2003/06/28
投稿数: 219
投稿日時: 2003-08-12 11:39
こんにちは、Ken-Labです。
自分ツッコミですが、先のソースにおきましてupdateメソッドとpaintメソッドの使い方が
逆だったようです(ただしどちらでも結果は変わりません)。
# アプレットプログラミングから離れていたもので・・すいません。
public void update (Graphics g){
 paint(g);
}
public void paint (Graphics g){
// 描画
}
それから、adjustmentValueChangedメソッドにおけるrepaintはrunメソッドを止めない限り
必要ないです。

# 描画効率の問題により、この現象が発生しているということですが・・・。

描画自体を軽量化して、見かけ上問題解決しただけでは不十分であり、重要なのは
乱れを防止する根本対策であると考えます。
(その点ではほむら様の意見と同じです。)
そうしないと、画像サイズが大きい場合や描画部分が複雑になった時、また同じ問題を生じる
恐れがあります。

# 表現が不明瞭だったため編集しました。

[ メッセージ編集済み 編集者: Ken-Lab 編集日時 2003-08-12 20:36 ]
ボム
ベテラン
会議室デビュー日: 2003/07/25
投稿数: 61
投稿日時: 2003-08-18 09:51
こんにちは、Casperです。
Ken-Lab様、かずくん様、さくらば様、ほむら様、ご意見ありがとうございました。
そして、返信が遅くなりました事をお許しください。

さて早速ですが、
かずくん様より引用************************************************************
>まず作成する機能をイメージさせてください。
・5枚のパネル(A〜E)がフレーム上にあります。
・各パネルには絵が張られており、その上に文字列も書かれております。
・キーボードの上下でパネルのフォーカスを移動させています。
・各パネルはフォーカスを取得すると、スレッドが開始され文字列が右から左へ移動します。
・各パネルはフォーカスを失うとスレッドが停止されます。
・repaint()はフォーカス取得時、フォーカス損失時、スレッド開始時に行われております。
*****************************************************************************
さくらば様より引用
>repaint のコールが多すぎるのだと私も思います。
フォーカス取得時、フォーカス損失時にはrepaint()する必要はないのですね?
*****************************************************************************
ほむら様より引用
>ここでいう永続とは次回paint()が呼び出されるまで残るかどうかということです。
はい。例えばフォーカス取得時、新たな文字列を描画しスクロール開始するのですが、
描画が失敗しているのか古い文字列が残ったまま、その上を新しい文字列がスクロールして
しまいます。
しかし、そのパネルがフォーカスを失うとパネルにrepaint()が行われ正常に描画されます。
ですから、ほむら様の"次回paint()が呼び出されるまで残るかどうか"という質問には、ハイ
次回repaint()が呼ばれるまで残ります。
******************************************************************************
Ken-Lab様より引用
>やはりclearRectがないと崩れるようでした。
しかし、"clearRect"をいれると描画がちらつきます。
細かなソースまでご提示いただきありがとうございました。
******************************************************************************

未記入
大ベテラン
会議室デビュー日: 2003/06/28
投稿数: 219
投稿日時: 2003-08-18 10:03
引用:

Casperさんの書き込み (2003-08-18 09:51) より:
>やはりclearRectがないと崩れるようでした。
しかし、"clearRect"をいれると描画がちらつきます。
細かなソースまでご提示いただきありがとうございました。


ちらつきですが、updateメソッドを入れても解消しませんでしたか?

スキルアップ/キャリアアップ(JOB@IT)