9 Canvas APIの使用
この章では、JavaFX Canvas APIについて説明し、コンパイルして実行できるコード例を示します。「グラフィックス・チュートリアルのソース・コード」ページにあるリンクを使用して、NetBeans IDEプロジェクトの例をダウンロードします。
概要
JavaFX Canvas APIによって、書込み可能なカスタム・テクスチャが提供されます。これは、javafx.scene.canvas
パッケージ内のクラスCanvas
およびGraphicsContext
によって定義されています。このAPIの使用には、Canvas
オブジェクトの作成、そのGraphicsContext
の取得、および画面にカスタム図形をレンダリングするための描画操作の起動が含まれます。Canvas
はNode
サブクラスであるため、JavaFX Sceneグラフで使用できます。
基本図形の描画
BasicOpsTest
プロジェクト(図9-1を参照)によって、Canvas
が作成され、そのGraphicsContext
が取得され、基本図形が描画されます。GraphicsContext
クラスのメソッドを使用すると、線、楕円、角丸四角形、円弧および多角形をすべて使用できます。完全なBasicOpsTest
NetBeansプロジェクトについては、BasicOpsTest.zipファイルをダウンロードしてください。
例9-1 キャンバスでの基本図形の描画
package canvastest; import javafx.application.Application; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; import javafx.scene.shape.ArcType; import javafx.stage.Stage; public class BasicOpsTest extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setTitle("Drawing Operations Test"); Group root = new Group(); Canvas canvas = new Canvas(300, 250); GraphicsContext gc = canvas.getGraphicsContext2D(); drawShapes(gc); root.getChildren().add(canvas); primaryStage.setScene(new Scene(root)); primaryStage.show(); } private void drawShapes(GraphicsContext gc) { gc.setFill(Color.GREEN); gc.setStroke(Color.BLUE); gc.setLineWidth(5); gc.strokeLine(40, 10, 10, 40); gc.fillOval(10, 60, 30, 30); gc.strokeOval(60, 60, 30, 30); gc.fillRoundRect(110, 60, 30, 30, 10, 10); gc.strokeRoundRect(160, 60, 30, 30, 10, 10); gc.fillArc(10, 110, 30, 30, 45, 240, ArcType.OPEN); gc.fillArc(60, 110, 30, 30, 45, 240, ArcType.CHORD); gc.fillArc(110, 110, 30, 30, 45, 240, ArcType.ROUND); gc.strokeArc(10, 160, 30, 30, 45, 240, ArcType.OPEN); gc.strokeArc(60, 160, 30, 30, 45, 240, ArcType.CHORD); gc.strokeArc(110, 160, 30, 30, 45, 240, ArcType.ROUND); gc.fillPolygon(new double[]{10, 40, 10, 40}, new double[]{210, 210, 240, 240}, 4); gc.strokePolygon(new double[]{60, 90, 60, 90}, new double[]{210, 210, 240, 240}, 4); gc.strokePolyline(new double[]{110, 140, 110, 140}, new double[]{210, 210, 240, 240}, 4); } }
例9-1に示すように、Canvas
は幅300および高さ250でインスタンス化されます。次に、そのGraphicsContext
が、canvas.getGraphicsContext2D()
のコールによって取得されます。その後、strokeLine
、fillOval
、strokeArc
、fillPolygon
などのメソッドを呼び出すことによって、多数の基本描画操作が実行されます。
グラデーションおよびシャドウの適用
次の例(CanvasTest
プロジェクト)では、カスタム図形をグラデーションおよびシャドウとともに描画することによって、GraphicsContext
のメソッドをさらにテストします。最終結果は、図9-2に示すようになります。完全なCanvasTest
NetBeansプロジェクトについては、CanvasTest.zipファイルをダウンロードしてください。
この例のコードは、各描画操作が固有のprivateメソッドで実行されるように構成されています。これにより、対象となるメソッドを呼び出す(またはコメント・アウト)だけで、様々な機能をテストできます。Canvas
APIの学習という点では、重点を置くコードは、基礎となるCanvas
またはGraphicsContext
オブジェクトのコールであることに注意してください。
このパターンには、5つの主要な部分があります。
最初に、Canvas
の位置が座標(0,0)
で設定されます。これは、平行移動変換を基礎となるCanvas
オブジェクトに適用する、例9-2のコードを呼び出すことで行われます。
例9-2 キャンバスの移動
private void moveCanvas(int x, int y) { canvas.setTranslateX(x); canvas.setTranslateY(y); }
その他の値をパラメータとして渡して、Canvas
を新しい位置に移動できます。渡した値はsetTranslateX
およびsetTranslateY
メソッドに転送され、Canvas
はそれに応じて移動します。
次に、(大文字のDのような)最初の図形が画面に描画されます。これは、GraphicsContecxt
オブジェクトのbezierCurveTo
メソッドで呼び出されるベジエ曲線で行われます。
例9-3 画面でのベジエ曲線(大文字のD)の描画
private void drawDShape() { gc.beginPath(); gc.moveTo(50, 50); gc.bezierCurveTo(150, 20, 150, 150, 75, 150); gc.closePath(); }
パラメータ値を変更することによって、この図形を試すことができます。bezierCurveTo
では、拡大したり引っ張ると図形がそれに応じて拡大または引っ張られます。
その後で、赤および黄のRadialGradient
によって、背景に表示される円形パターンが指定されます。
例9-4 RadialGradientの描画
private void drawRadialGradient(Color firstColor, Color lastColor) { gc.setFill(new RadialGradient(0, 0, 0.5, 0.5, 0.1, true, CycleMethod.REFLECT, new Stop(0.0, firstColor), new Stop(1.0, lastColor))); gc.fill(); }
ここで、GraphicsContext
のsetFill
メソッドは、そのパラメータとしてRadialGradient
オブジェクトを受け入れます。再度、異なる値を試すか、異なる色を渡すことができます。
LinearGradient
では、カスタムD図形に青から緑の色が付けられます。
例9-5 LinearGradientの描画
private void drawLinearGradient(Color firstColor, Color secondColor) { LinearGradient lg = new LinearGradient(0, 0, 1, 1, true, CycleMethod.REFLECT, new Stop(0.0, firstColor), new Stop(1.0, secondColor)); gc.setStroke(lg); gc.setLineWidth(20); gc.stroke(); }
このコードでは、GraphicsContext
の線はLinearGradient
を使用するように設定され、パターンはgc.stroke()
を使用してレンダリングされます。
最後に、GraphicContext
オブジェクトでapplyEffect
を呼び出して、多色のドロップ・シャドウが指定されます。
例9-6 DropShadowの追加
private void drawDropShadow(Color firstColor, Color secondColor, Color thirdColor, Color fourthColor) { gc.applyEffect(new DropShadow(20, 20, 0, firstColor)); gc.applyEffect(new DropShadow(20, 0, 20, secondColor)); gc.applyEffect(new DropShadow(20, -20, 0, thirdColor)); gc.applyEffect(new DropShadow(20, 0, -20, fourthColor)); }
例9-6に示すように、この効果を適用するには、指定の色を使用してDropShadow
オブジェクトを作成し、このオブジェクトをGraphicsContext
オブジェクトのapplyEffect
メソッドに渡します。
ユーザーとの対話
次のデモ(プロジェクトCanvasDoodleTest
)では、青い正方形が画面に表示され、ユーザーがその表面でマウスをドラッグするとゆっくり消されていきます。完全なCanvasDoodleTest
NetBeansプロジェクトについては、CanvasDoodleTest.zipファイルをダウンロードしてください。
基本図形およびグラデーションの作成方法について説明しました。例9-7のコードでは、ユーザーとの対話部分にのみ焦点を当てています。
例9-7 ユーザーとの対話
... private void reset(Canvas canvas, Color color) { GraphicsContext gc = canvas.getGraphicsContext2D(); gc.setFill(color); gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight()); } @Override public void start(Stage primaryStage) { ... final GraphicsContext gc = canvas.getGraphicsContext2D(); ... // Clear away portions as the user drags the mouse canvas.addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent e) { gc.clearRect(e.getX() - 2, e.getY() - 2, 5, 5); } }); // Fill the Canvas with a Blue rectnagle when the user double-clicks canvas.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent t) { if (t.getClickCount() >1) { reset(canvas, Color.BLUE); } } }); ...
例9-7では、四角形全体をデフォルトの青色で塗りつぶすreset
メソッドを定義します。しかし、最も興味深いコードは、ユーザーとの対話のためにオーバーライドされるstart
メソッド内にあります。最初のコメントがあるセクションでは、ユーザーがマウスをドラッグしたときにMouseEvent
オブジェクトを処理するイベント・ハンドラが追加されます。ドラッグのたびに、GraphicsContext
オブジェクトのclearRect
メソッドが呼び出され、現在のマウス座標および消去される領域のサイズが渡されます。これが発生すると、図9-4に示すように、背景のグラデーションが透けて表示されます。
残りのコードでは、単にクリック数をカウントし、ユーザーがマウスをダブルクリックした場合に青い正方形を元の状態にリセットします。
単純なレイヤー・システムの作成
複数のCanvas
オブジェクトをインスタンス化し、これらを使用して単純なレイヤー・システムを定義することもできます。したがって、レイヤーの切替えでは、目的のCanvas
の選択とそこへの書込みが問題になります。(Canvas
オブジェクトは完全に透明であり、その一部の上で描画するまでは透けて表示されます。)
この最後のデモ(LayerTest
プロジェクト)では、2つのCanvas
オブジェクトを追加および作成し、直接重ね合せて配置することで、そのようなシステムが定義されます。画面をクリックすると、現在選択されているレイヤー上に色付きの円が表示されます。画面の上部でChoiceBox
を使用することで、レイヤーを変更できます。レイヤー1に追加される円は緑です。レイヤー2に追加される円は青です。完全なLayerTest
NetBeansプロジェクトについては、LayerTest.zipファイルをダウンロードしてください、
このデモのGUIでは、コンポーネントを管理するためにBorderPane
が使用されます。ChoiceBox
が上部に追加され、Panel
は2つのCanvas
オブジェクトが追加されてから画面の中央に追加されます。
例9-8 レイヤーの作成および追加
... private void createLayers(){ // Layers 1&2 are the same size layer1 = new Canvas(300,250); layer2 = new Canvas(300,250); // Obtain Graphics Contexts gc1 = layer1.getGraphicsContext2D(); gc1.setFill(Color.GREEN); gc1.fillOval(50,50,20,20); gc2 = layer2.getGraphicsContext2D(); gc2.setFill(Color.BLUE); gc2.fillOval(100,100,20,20); } ... private void addLayers(){ // Add Layers borderPane.setTop(cb); Pane pane = new Pane(); pane.getChildren().add(layer1); pane.getChildren().add(layer2); layer1.toFront(); borderPane.setCenter(pane); root.getChildren().add(borderPane); } ...
ユーザーとの対話は、各レイヤーにイベント・ハンドラを直接追加することによって達成できます。Canvas
をクリックするとMouseEvent
が生成され、これが受け取られると現在のマウスの位置に円が描画されます。
例9-9 イベント・ハンドラの追加
private void handleLayers(){ // Handler for Layer 1 layer1.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent e) { gc1.fillOval(e.getX(),e.getY(),20,20); } }); // Handler for Layer 2 layer2.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent e) { gc2.fillOval(e.getX(),e.getY(),20,20); } }); }
両方のレイヤーが直接重ね合せて配置されているため、最前面のCanvas
のみでマウス・クリックが処理されます。特定のレイヤーを重なりの前面に移動するには、単純に、画面の上部にあるChoiceBox
コンポーネントから選択します。
例9-10 レイヤーの選択
private void createChoiceBox(){ cb = new ChoiceBox(); cb.setItems(FXCollections.observableArrayList( "Layer 1 is GREEN", "Layer 2 is BLUE")); cb.getSelectionModel().selectedItemProperty(). addListener(new ChangeListener(){ @Override public void changed(ObservableValue o, Object o1, Object o2){ if(o2.toString().equals("Layer 1 is GREEN")){ layer1.toFront(); }else if(o2.toString().equals("Layer 2 is BLUE")){ layer2.toFront(); } } }); cb.setValue("Layer 1 is GREEN"); }
例9-10に示すように、ChangeListener
がChoiceBox
で登録され、toFront()
を該当するCanvas
で呼び出すことで、選択したレイヤーが最前面に移動されます。レイヤーの選択は、青および緑の円を多数追加した後でレイヤーを切り替えるとよりわかりやすくなります。(円の端を見ることで)どちらのレイヤーが前面に移動されているかがわかります。図9-6および図9-7に、どのような表示になるかを示します。
レイヤーを選択する機能は、イメージ操作プログラムなどのソフトウェア・アプリケーションで一般的です。また、各Canvas
オブジェクトはNode
であるため、他のコンポーネントで適用するすべての標準的な変換およびビジュアル効果を自由に適用できます。