2007 年 2 月 6 日 23 時 11 分

RFBCanvas クラス


このアーカイブは同期化されません。 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;



Copyright (c) 1994-2007 Project Loafer. All rights reserved.