タイトルの通りです。大学の課題でJavaFXを使ったペインタを作成中です。

全体のソースはこちら。
kznrluk/4s-program-painter: ネットワークプログラミング応用演習 – 簡易ペインター

JavaFXのCanvasは他のCanvasと同じように、描写した内容を逐一保持しないため、Undo / Redoの処理には多少工夫が必要になります。

  • MOUSE_PRESSEDからMOUSE_RELEASEDまでの軌道を保持する。
  • Canvasの現時点の画像を取得して保持する。
  • Canvasのインスタンスをコピーする(実際にできるか不明)。

選択時点で軌道を保持して再描写する際にどれほどのコストがかかるのか不明だったため、Canvasの画像を取得する二番目を選びました。今考えてみると、画像で保持するのもそれはそれでコストがかかる気がしますが。

実装は比較的簡単です。履歴を保持するクラスを用意します。

JavaではArrayDequeという便利な配列があるので、それを使っています。

https://github.com/kznrluk/4s-program-painter/blob/master/src/Painter/Controller/ImageHistory.java

描写開始時に現在の描画内容を画像化して保存します。

this.canvas.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
    WritableImage snapshot = new WritableImage(CANVAS_SIZE_X, CANVAS_SIZE_Y);
    this.canvas.snapshot(null, snapshot);
    this.history.add(snapshot);

    currentPen.mousePressed(event);
});

Undo / Redo時にスタックされている要素を取得し、キャンバスに描写します。

void undo() {
    WritableImage image = this.history.getLast(this.canvas.snapshot(null, null));
    GraphicsContext gtx = this.canvas.getGraphicsContext2D();
    gtx.drawImage(image, 0, 0);
}

void redo() {
    WritableImage image = this.history.redo(this.canvas.snapshot(null, null));
    GraphicsContext gtx = this.canvas.getGraphicsContext2D();
    gtx.drawImage(image, 0, 0);
}

描写終了時ではなく、描写開始時に履歴を作成することがポイントです。描写終了時に履歴を作成すると、Undoする場合に現在の描画内容が一度出現してしまいます。

そのうちしっかり書き直したいですが忘備録として。とりあえず以上です。