JavaFXシェイプを使ったベクターグラフィック

JavaFXには、GUI部品のように画面へ配置できるベクターグラフィック部品がある。ここでは、それを利用したグラフィックの作成について説明する。

FXMLシェイプを使う

Canvasを使ったグラフィック描画は、感覚的にはSwingやAWTに似ている。言い換えると、グラフィックコンテキストを取得し、レンダリングメソッドを呼び出して画面に描画する方式である。描かれるグラフィックは単純なビットマップグラフィックであり、一度描けばそれで終わりである。

こうしたビットマップグラフィックとは別に、JavaFXにはベクターグラフィック用の機能も含まれている。ベクターグラフィックとは、位置やサイズなどのグラフィック情報を保持している図形である。後からそれを変更し、位置やサイズなどを操作できる。また、データとして作成され、必要に応じて再描画されるため、拡大縮小してもビットマップグラフィックのように図形が粗くならない。

このベクターグラフィックはjavafx.scene.shapeパッケージに含まれている。FXMLタグとして利用できる。簡単な使用例を見てみよう。

<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?>
<Pane xmlns="http://javafx.com/javafx"
    xmlns:fx="http://javafx.com/fxml"
    fx:controller="com.tuyano.libro.AppController">
    <Rectangle x="50" y="50" width="100" height="100"
        fill="RED" stroke="BLUE" strokeWidth="5"/>
    <Circle centerX="150" centerY="150" radius="50"
        fill="GREEN" stroke="CYAN" strokeWidth="5"/>
</Pane>

上の例は、四角形と円形を表示するものである。これをFXMLファイルに書き、JavaからロードしてSceneに表示する。たとえばapp.fxmlというファイルとして作成したなら、次のように実行すればよい。

Scene scene = new Scene(FXMLLoader.load(getClass().getResource("app.fxml")),300,300);
stage.setScene(scene);
stage.show();

ここでは赤い四角形と緑の円がウィンドウに表示される。今回はシェイプを使ってウィンドウ内に自由にグラフィックを配置するため、<BorderLayout>のようなレイアウトコンテナはあまり意味がない。そのため、<Pane>というコンテナをルートタグとして指定している。<Pane>はレイアウト機能を持たない、もっとも単純なコンテナである。シェイプのようにレイアウトが不要なコンポーネントを配置するときに使うとよい。

主なシェイプFXMLタグ

ここでは四角形と円形を作成した。これら以外にもさまざまなシェイプがある。重要なものを整理しておく。

四角形シェイプ Rectangle

例で使用した四角形シェイプである。

属性 説明
x 横位置を指定する。
y 縦位置を指定する。
width 幅を指定する。
height 高さを指定する。

円シェイプ Circle

これも例で使用した。円を表示するシェイプである。四角形とは属性が少し異なる。

属性 説明
centerX 中心の横位置を指定する。
centerY 中心の縦位置を指定する。
radius 半径を指定する。

楕円シェイプ Ellipse

楕円を描くシェイプである。円形と異なり、横方向と縦方向の半径をそれぞれ別に指定できる。

属性 説明
centerX 中心の横位置を指定する。
centerY 中心の縦位置を指定する。
radiusX 横半径を指定する。
radiusY 縦方向の半径を指定する。

円弧シェイプ Arc

円の一部だけを切り取ったような円弧を描く。楕円の属性に加えて、弧の大きさ、つまり角度に関する属性が追加されている。

属性 説明
centerX 中心の横位置を指定する。
centerY 中心の縦位置を指定する。
radiusX 横半径を指定する。
radiusY 縦方向の半径を指定する。
startAngle 弧の開始点の角度、0から360の値を指定する。
length 円弧の大きさを角度で指定する。
type 弧の種類。ROUNDCHORDOPENのいずれかを指定する。

直線シェイプ Line

2点を結ぶ直線を描くシェイプである。開始点と終了点の属性が必要になる。

属性 説明
startX 開始点の横位置を指定する。
startY 開始点の縦位置を指定する。
endX 終了点の横位置を指定する。
endY 終了点の縦位置を指定する。

シェイプ全般に関する属性

このほか、すべてのシェイプに共通して含まれる属性もある。基本的には塗りつぶしや線の情報である。線には端の形状など多くの属性があるが、まずは次の3つだけ覚えておこう。

属性 説明
fill 塗りつぶし色を指定する。
stroke 線の色を指定する。
strokeWidth 線の太さを指定する。

まずこれらを使えるようになれば、基本図形は描ける。実際にタグを書いて練習してみよう。

直線、曲線のシェイプ

円形や四角形はとても単純な図形だが、より複雑な図形が必要になることもある。そのような場合に使われるのが、直線の多角形や曲線のような図形である。

これらの図形は位置情報に関する属性が多く必要になり、その分記述も複雑になる。使い方を整理しておこう。

直線、多角形のシェイプ PolygonPolyline

複数の点を直線で結ぶ多角形を描くためのものである。Polygonは開始点と終了点を接続する閉じた図形を描き、Polylineは両端を結ばない開いた図形を描く。基本的な使い方はどちらも同じである。

FXMLで記述する場合は、開始タグと終了タグの間に<points>タグを用意し、その中に<Double>タグを使って各位置の横位置と縦位置の情報を順番に書いていく。

曲線シェイプ QuadCurveCubicCurve

曲線には2種類ある。QuadCurveは2次曲線を描くためのものである。開始点と終了点の2点に加え、1つのコントロールポイントを使って描かれる。円弧のような曲線である。

CubicCurveは3次曲線を描くためのものである。開始点と終了点に加え、2つのコントロールポイントを使って描かれる。いわゆるベジエ曲線と呼ばれるものがこれに当たる。

これらは開始点、終了点、コントロールポイントの位置をすべて属性として記述する。

属性 説明
startX 開始点の横位置を指定する。
startY 開始点の縦位置を指定する。
endX 終了点の横位置を指定する。
endY 終了点の縦位置を指定する。

QuadCurveの場合

属性 説明
controlX コントロールポイントの横位置を指定する。
controlY コントロールポイントの縦位置を指定する。

CubicCurveの場合

属性 説明
controlX1 第1コントロールポイントの横位置を指定する。
controlY1 第1コントロールポイントの縦位置を指定する。
controlX2 第2コントロールポイントの横位置を指定する。
controlY2 第2コントロールポイントの縦位置を指定する。

次はPolygonCubicCurveを表示するFXMLサンプルである。

<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.shape.Ellipse?>
<?import javafx.scene.shape.Line?>
<?import javafx.scene.shape.Polygon?>
<?import javafx.scene.shape.CubicCurve?>
<Pane xmlns="http://javafx.com/javafx"
    xmlns:fx="http://javafx.com/fxml"
    fx:controller="com.devkuma.javafx.AppController">
 
    <Polygon fill="RED">
    <points>
        <Double fx:value="110.0" />
        <Double fx:value="10.0" />
        <Double fx:value="210.0" />
        <Double fx:value="20.0" />
        <Double fx:value="150.0" />
        <Double fx:value="100.0" />
    </points>
    </Polygon>
 
    <CubicCurve fill="YELLOW" stroke="BLUE" strokeWidth="5"
        startX="50" startY="50" endX="200" endY="200"
        controlX1="200" controlY1="50"
        controlX2="50" controlY2="200" />
 
</Pane>

<Polygon>タグの中に<points>タグがあり、さらにその中で<Double>タグを使って各頂点の横位置と縦位置を順番に書いている。書き方が少し理解しづらいので、タグ構造をよく理解して使う必要がある。

Javaソースコードでシェイプを使う

FXMLを使わず、Javaソースコードから直接シェイプのオブジェクトを作り、ステージに設定して表示することもできる。実際に試してみよう。

サンプルは次のとおりである。

package com.devkuma.javafx;
     
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
 
public class App extends Application {
     
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        Pane root = new Pane();
        createShape(root);
        Scene scene = new Scene(root,300,300);
        stage.setScene(scene);
        stage.show();
    }
     
    public void createShape(Pane root){
        Rectangle r = new Rectangle(50, 50, 100, 100);
        r.setFill(Color.RED);
        r.setStroke(Color.BLUE);
        r.setStrokeWidth(3);
        root.getChildren().add(r);
        Circle c = new Circle(150, 150, 50);
        c.setFill(Color.YELLOW);
        c.setStroke(Color.GREEN);
        c.setStrokeWidth(10);
        root.getChildren().add(c);
    }
}

ここではcreateShapeメソッドを使い、その中で四角形と円形のシェイプを描いている。先ほどFXMLで作ったサンプルと同じものなので、両者を比較すると違いがよくわかる。

インスタンスを作成する

シェイプのクラスは、RectangleCircleのようにFXMLタグと同じ名前である。インスタンスの生成は、必要な属性を引数として指定する形になっている。

new Rectangle(横位置, 縦位置, 幅, 高さ)
new Circle(中心横位置, 中心縦位置, 半径)

これらのクラスには引数のないデフォルトコンストラクタを含め、複数のコンストラクタが存在する。必須項目となる属性値をすべて引数に入れる形がもっともわかりやすい。

属性を設定する

作成後、fillstrokestrokeWidthの属性を設定するメソッドを呼び出す。Rectangleなら次のようになる。

r.setFill (Color.RED);
r.setStroke (Color.BLUE);
r.setStrokeWidth (3);

FXMLタグに含まれていた属性は、「set + 属性名」というメソッドで値を設定できる。また今回は使っていないが、「get + 属性名」または「is + 属性名」というメソッドで値を取得できる。

色の値について

今回のsetFillsetStrokeではColorクラスのフィールドを指定している。これは先にCanvasでも利用した。塗りつぶしや線の色をColorで設定するという基本的な考え方は、Canvas描画とほぼ同じである。

クリッピングで切り抜く

基本図形の作り方はだいたいわかった。さらに複雑な図形を作るために知っておきたい機能もいくつか補足しておこう。

まずはクリッピングである。クリッピングとは、ウィンドウに「窓」を開け、そこから表示するようなものだ。要するに、描かれたグラフィックの一部だけを切り抜いて表示できる。

このクリッピングは、NodeクラスにあるsetClipメソッドで設定できる。NodeクラスはBorderLayoutPaneなどのコンテナ類、さらにここで扱ったシェイプ類のスーパークラスでもある。

"Node".setClip("Node");

setClipは引数にNodeインスタンスを指定する。それにより、その部品に対して引数ノードの形状をクリッピング領域として設定する。部品の表示は、引数に指定したノード形状で切り抜かれたものになる。引数ノードの形そのものは外部には描かれない。

実際に試してみよう。次の例のように、以前のcreateShapeメソッドを修正して使用する。

public void createShape(Pane root){
    Rectangle r = new Rectangle(50, 50, 100, 100);
    r.setFill(Color.RED);
    r.setStroke(Color.BLUE);
    r.setStrokeWidth(3);
    root.getChildren().add(r);
    Circle c = new Circle(150, 150, 50);
    c.setFill(Color.YELLOW);
    c.setStroke(Color.GREEN);
    c.setStrokeWidth(10);
    root.getChildren().add(c);
    Circle clip = new Circle(120, 120, 75);
    root.setClip(clip);
}

ここではRectangleCircleを作成してrootに設定した後、クリッピング用のCircleを作成している。

root.setClip(clip);

そして上のようにクリッピングとして設定している。すると、setClipしたCircleの円形に切り抜かれてグラフィックが表示される。

Pathで複雑な図形を作成する

複雑な図形を描くもう1つの方法として、Pathを利用できる。Pathは、一筆書きのように直線や曲線を描いていくことで図形を作成するものである。

これはPathクラスを使って作成する。Pathインスタンスを作り、そこに線グラフィックとなるインスタンスを作成して設定していく。Pathに設定できるグラフィック部品はPathElementクラスのサブクラスで、次のようなものがある。

描画位置を移動するMoveTo

new MoveTo(横位置, 縦位置)

一気に描画位置を移動する。引数で指定した位置へレンダリング位置を移動する。

直線を描くLineTo

new LineTo(終端横位置, 終端縦位置)

現在の描画位置から指定した地点まで直線を描く。引数には線の終端位置を指定する。

2次曲線を描くQuadCurveTo

new new QuadCurveTo(CP横位置, CP縦位置, 終端横位置, 終端縦位置)

現在の描画位置から2次曲線を描く。引数にはコントロールポイントの位置と終了位置を指定する。

3次曲線を描くCubicCurveTo

new new QuadCurveTo(CP横位置1, CP縦位置1, CP横位置2, CP縦位置2, 終端横位置, 終端縦位置)

現在の描画位置から3次曲線を描く。引数には2つのコントロールポイントの位置と終了位置を指定する。

では実際の使用例を見てみよう。次のリストは、先ほどのサンプルcreateShapeメソッドを修正したものである。これにより、2本の直線と1本の3次曲線で構成されたPath図形を作成して表示する。

public void createShape(Pane root){
    Path path = new Path();
    MoveTo mt1 = new MoveTo(50, 50);
    path.getElements().add(mt1);
    LineTo lt1 = new LineTo(250, 50);
    path.getElements().add(lt1);
    CubicCurveTo cc1 = new CubicCurveTo(250, 250,50, 50, 50, 250);
    path.getElements().add(cc1);
    LineTo lt2 = new LineTo(50, 50);
    path.getElements().add(lt2);
    path.setFill(Color.RED);
    root.getChildren().add(path);
}

ここではPathインスタンスを作成し、MoveToLineToCubicCurveToなどのインスタンスを追加している。図形の接続は次のように行う。

path.getElements().add(mt1);

PathgetElementsメソッドは、Pathに設定されているList (ObservableList)インスタンスを取得するために使われる。

PathはこのListで描画する図形のPathElementを管理しており、ここにaddメソッドでインスタンスを追加していくことで、描画する図形が追加されていく。単純な図形の組み合わせでは描けない複雑な形状も、Pathを利用すれば描画できる。