このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。
昨日の考察に基づいて RFBCanvas を作ってみよう。
========== RFBCanvas.java ==========
package jp.loafer.rfb.server;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import jp.loafer.rfb.RFBContext;
import jp.loafer.rfb.io.RFBOutputStream;
import jp.loafer.rfb.message.PixelFormat;
import jp.loafer.rfb.message.client.*;
import jp.loafer.rfb.server.BaseRFBDisplay;
/**
* AWT に似た描画メカニズムを持つ RFB の仮想画面。
* @author kes
*/
public abstract class RFBCanvas extends BaseRFBDisplay {
/**
* 仮想画面を作成。
* @param width 画面の幅。
* @param height 画面の高さ。
* @param name 画面の名前。
*/
public RFBCanvas(int width, int height, String name) {
this(width, height, name, null);
}
/**
* 仮想画面を作成。
* @param width 画面の幅。
* @param height 画面の高さ。
* @param name 画面の名前。
* @param defaultPixelFormat 画面の既定の画素形式。
*/
public RFBCanvas(int width, int height,
String name, PixelFormat defaultPixelFormat) {
super(width, height, name, defaultPixelFormat);
clientRegion = new Rectangle(0, 0, width, height);
paintingSystem = new Object();
updateTimer = new Timer("RFBCanvas painting system", true);
}
/**
* 画面全体の再描画を行う。
*/
public void repaint() {
repaint(0, 0, getWidth(), getHeight());
}
/**
* 画面の一部の再描画を行う。
* @param x 矩形領域の左上の X 座標。
* @param y 矩形領域の左上の Y 座標。
* @param width 矩形領域の幅。
* @param height 矩形領域の高さ。
*/
@SuppressWarnings("hiding")
public void repaint(int x, int y, int width, int height) {
// 無効になった領域を計算
Rectangle r = new Rectangle(x, y, width, height);
if (r.isEmpty()) return;
// ウィンドウ範囲は超越できない
r = r.intersection(clientRegion);
if (r.isEmpty()) return;
// タイマー利用で非ブロッキング呼び出し
final Rectangle region = r;
updateTimer.schedule(new TimerTask() {
@Override
public void run() {
// 直ちに画面を更新し
fireUpdate(region);
// 可能なら更新内容を送信する
try {
sendFramebuffer();
} catch (IOException e) { // FIXME
e.printStackTrace();
throw new RuntimeException(e);
}
}
}, 0);
}
//==================================================
// イベントハンドラ(具象実装用)
//==================================================
// メインの描画処理
protected abstract void paint(Graphics2D g);
// メインの更新描画処理
protected void update(Graphics2D g) {
// 既定では paint を呼び出す
paint(g);
}
//==================================================
// メッセージハンドラ(基底実装用)
//==================================================
/**
* @see BaseRFBDisplay#initialize(RFBContext, RFBOutputStream)
*/
@Override
@SuppressWarnings("hiding")
public void initialize(RFBContext context, RFBOutputStream out) throws IOException {
super.initialize(context, out);
// 画面バッファを用意する
recreateDisplayBuffer();
}
/**
* @see BaseRFBDisplay#handleSetPixelFormat(SetPixelFormatMessage)
*/
@Override
public void handleSetPixelFormat(SetPixelFormatMessage message) throws IOException {
super.handleSetPixelFormat(message);
// 画素形式が変化したので画面バッファを再作成
recreateDisplayBuffer();
}
/**
* @see BaseRFBDisplay#handleFramebufferUpdateRequest(FramebufferUpdateRequestMessage)
*/
@Override
public void handleFramebufferUpdateRequest(FramebufferUpdateRequestMessage message) throws IOException {
synchronized (paintingSystem) {
// 要求された領域を記憶
requestedRegion = new Rectangle(
message.getX(), message.getY(),
message.getWidth(), message.getHeight());
// もし完全描画の要求ならば paint を発行
if (!message.isIncremental())
firePaint(requestedRegion);
// 変更があれば画面データをクライアントに送る
sendFramebuffer();
}
}
//==================================================
// ヘルパーメソッド
//==================================================
// クライアントの画素形式に合った画面バッファを用意
private void recreateDisplayBuffer() throws IOException {
}
// 指定範囲に対して paint を発行
private void firePaint(Rectangle region) {
}
// 指定範囲に対して update を発行
void fireUpdate(Rectangle region) {
}
// 必要に応じて画面の変更を送信
void sendFramebuffer() throws IOException {
}
// クライアント領域
private Rectangle clientRegion;
// 描画系の同期用
private Object paintingSystem;
// 非ブロッキング update 用
private Timer updateTimer;
// 画面データバッファ
private BufferedImage displayBuffer;
// 未送信の画面領域
private Shape unsentRegion; // Area or Rectangle
// 送信を要求された領域
private Rectangle requestedRegion;
}
========== end of RFBCanvas.java ==========
とりあえず外向きのメソッドを実装するとこのようになる。
repaint の実装には少し無理があるような気もするが、
タイマーを使って update を呼び出すことで、
repaint はブロックせずにすぐに戻るようにできる。
フィールド updateTimer はそのために用意している。
また、タイマーを使う副作用として、
スレッドの同期上の問題が生じるため、
制御するためのフィールド paintingSystem も用意した。
handleFramebufferUpdateRequest では、
増分か完全かを調べ、完全ならば paint を発行、
そして、sendFramebuffer を呼んでデータを送信する。
次は、これらを支えるヘルパメソッドを実装せねば。
private void recreateDisplayBuffer() throws IOException;
private void firePaint(Rectangle region);
void fireUpdate(Rectangle region);
void sendFramebuffer() throws IOException;