グラフィックスを扱う |
一定の間隔で描画することによって動画を実行するプログラムでは動画ループが必要 となる。 一般に、このループはそれ独自のスレッドに置くべきであって、 決して、 paint() または update() メソッドに置いては ならない。 このようなメソッドは、描画とイベント処理のすべてをつかさどるメインの AWT スレッドを使うからである。
このページでは動画を実行するときのテンプレートを 2 つ紹介する。1 つはアプレ ット用、もう 1 つはアプリケーション用である。 すぐ下で、アプレットバージョンが動作している。 クリックすると動画が停止され、もう一度クリックすると再開される。
使用中のブラウザは 1.0 Java アプレットを実行することができない。したがってここでは実行中のスナップショットを示す。
このテンプレートが実行する動画は、たいしておもしろくはない。1 秒当たり 10 フ レームというデフォルトのレートで、現在のフレーム番号を表示するだけである。この後のレッスンではこの例をもとに、プリミティブなグラフィックスとイメージを動画化する方法を示していく。
アプレットの動画テンプレートのコード も参照できる。 同等の アプリケーションの動画テンプレート のコードも用意されている。 このページの残りの部分では、テンプレートのコードについて解説する。 以下に、両方のテンプレートの実行内容を要約する。
public class AnimatorClass extends AComponentClass implements Runnable { //初期化コードで: //ユーザ指定の 1 秒当たりのフレーム数をもとに、フレーム間で //どのくらい遅延させるかを決定する。 //動画を開始するだけのメソッドで: //動画スレッドを作成し起動する。 //動画を停止するだけのメソッドで: //動画スレッドを停止する。 public boolean mouseDown(Event e, int x, int y) { if (/* 動画が現在凍結されている場合 */) { //動画を開始するメソッドを呼び出す。 } else { //動画を停止するメソッドを呼び出す。 } } public void run() { //他の処理の進行を妨げないよう、 //このスレッドの優先順位を低くする。 //開始時刻を記録しておく。 //これが動画ループである。 while (/* 動画スレッドが動作している間 */) { //動画フレームを先に進める。 //それを表示する。 //所定の遅延時間に応じて待機する。 } } public void paint(Graphics g) { //動画の現在のフレームを描画する。 } }インスタンス変数を初期化する
動画テンプレートは 4 つのインスタンス変数を使用する。 最初のインスタンス変数 (frameNumber) は現在のフレームを表す。 最初のフレーム番号は 0 だが、この変数は -1 に初期化される。 この理由は、フレームのペイントより前の、動画ループの開始時に、フレーム番号が 増分されるからである。 したがって、ペイントされる最初のフレームはフレーム 0 となる。
2 番目のインスタンス変数 (delay) はフレーム間のミリ秒数である。 ユーザが指定した1 秒当たりのフレーム 数をもとに初期化される。 ユーザが有効な数値を指定していない場合は、テンプレートは 1 秒当たり 10 フレームをデフォルトとする。 次のコードは、1 秒当たりのフレーム数をフレーム間のミリ秒数に変換するものであ る。
delay = (fps > 0) ? (1000 / fps) : 100;前のコード片にある ? : 表記は、if else の短縮形である。 ユーザが 1 秒当たりのフレーム数に 0 より大きい数を指定した場合は、遅延時間は 、その 1 秒当たりのフレーム数で 1000 ミリ秒を割った値となる。 そうでない場合は、フレーム間の遅延時間は 100 ミリ秒である。
3 番目のインスタンス変数 (animatorThread) は、動画ループが動作 するスレッドを表す Thread オブジェクトである。 スレッドのことがよくわからない場合は、使用法が概説されているスレッドのコントロール レッスンを参照する。
4 番目のインスタンス変数 (frozen) は false に初期 化される論理値である。 テンプレートがこの値を trueに設定するのは、ユーザが動画の停止を 要求したときである。 これについては、このセクションの後の方でもう少し説明する。
動画ループ
動画ループ (動画スレッド中の while ループ) は、以下の作業を何度も 繰り返す。
- フレーム番号を進める。
- repaint() メソッドを呼び出し、動画の現在のフレームを描画す るよう要求する。
- delay ミリ秒に達するまでスリープする (この詳細は後述する) 。
以下に、これらのタスクを実行するコードを示す。
while (/* 動画スレッドが動作している間 */) { //動画フレームを進める。 frameNumber++; //動画フレームを表示する。 repaint(); ...//所定の遅延時間に応じて待機する。 }フレームのレートを一定にする
動画ループでスリープを実装する最もわかりやすい方法は、delay ミ リ秒の間スリープさせることである。 ただしこの方法ではスレッドのスリープが長くなりすぎることがある。これは、動画 ループの実行にかかる時間が計算に入っていないからである。
この問題の解決策は、動画ループの開始時刻を記録し、 それに delay ミリ秒を加えて覚醒時刻とし、 その覚醒時刻までスリープすることである。 以下に、これを実装したコードを示す。
long startTime = System.currentTimeMillis(); while (/* 動画スレッドが動作している間 */) { ...//動画フレームを進め、表示する。 try { startTime += delay; Thread.sleep(Math.max(0, startTime-System.currentTimeMillis())); } catch (InterruptedException e) { break; } }優雅に振る舞う
動画テンプレートのあと 2 つの機能は優雅な振る舞いに関することである。
その 1 つは、アプレットまたはアプリケーションが表示されている間に、ユーザが 動画を明示的に停止 (および再開) できるようにすることである。 動画にはかなり目を引かれるため、ユーザが何か別のことに集中できるように動画を 停止させられることは大切である。この機能は、動画スレッドの現在の状態に応じて そのスレッドを停止または再開するよう、 mouseDown() メソッドを上書きすることによって実装されている。 以下に、これを実装しているコード部分を示す。
...//初期化コードで: boolean frozen = false; ...//動画スレッドを開始するメソッドで: if (frozen) { //何もしない。イメージの変更を停止するよう //ユーザから要求されている。 } else { //動画を開始する! ...//動画スレッドを作成し起動する。 } } . . . public boolean mouseDown(Event e, int x, int y) { if (frozen) { frozen = false; //動画を開始するメソッドを呼び出す。 } else { frozen = true; //動画を停止するメソッドを呼び出す。 } return true; }もう 1 つの機能は、アプレットまたはアプリケーションが可視でないことがわかっ ている場合は必ず、動画を一時停止することである。 アプレットの動画テンプレートでは、これは Applet の stop() およ び start() メソッドを実装することによって実現される。 アプリケーションの動画テンプレートでは、これは WINDOW_ICONIFY および WINDOW_DEICONIFY イベントを処理するイベントハンドラを実装することによって実 現される。 どちらのテンプレートでも、ユーザが動画を凍結していなければ、プログラムが動画 が可視でないことを検出した時点で動画スレッドに停止を指示する。 ユーザが明示的に動画の停止を要求していないかぎり、ユーザが動画を再訪したときにプログラムは動画スレッドを再開する。
フレーム番号の増分をなぜループの末尾ではなく先頭で行うのか不思議に思われるか もしれない。 この理由は、ユーザが動画を凍結し、そこを出て、後で再訪するときに起こることに 対処するためである。ユーザが動画を凍結すると、動画ループは終了前に完結してい まう。 フレーム番号がループの先頭ではなく末尾で増分されるとすると、ループの出口のフレーム番号が表示中のフレームより 1 つ大きい数になってしまうのである。 ユーザが動画を再訪すると、ユーザが出たときとは異なるフレームが凍結されている ことになる。これは混乱の元となりかねないし、ユーザが動画を停止して特定のフレームを見ようとする場合にはまったく迷惑な動きである。
グラフィックスを扱う |