LoginSignup
1
2

More than 1 year has passed since last update.

自作のPythonのSVG用ライブラリでパスの描画をサポートしました。

Posted at

趣味で作っているPythonのSVGベースのapyscというフロント用のライブラリでベジェ曲線などを含めたパスの描画をサポートしたので記事にしておきます。

ライブラリの概要

パスの描画機能の前にライブラリの基本情報について軽く触れておきます。

関連ページ

GitHub:

日本語ドキュメント:

今回追加になったパス関係のドキュメント:

インストール

pipでインストールができます。本記事では執筆時点で最新のv2.5.20を使っていきます。

$ pip install apysc==2.5.20

本記事の実行環境

本記事執筆用には楽なのでColaboratoryを使っていきます。

ノートブック上で以下のように記述することでインストールができます。

!pip install apysc==2.5.20

image.png

後はそのままColaboratory上で動かせます。display_on_colaboratoryという関数でColaboratory上で結果を表示できます。以下の例ではサンプルとしてシアン色の四角と丸を描画しています。

import apysc as ap

ap.Stage(stage_width=200, stage_height=150, background_color="#555")
ap.Rectangle(x=50, y=50, width=50, height=50, fill_color="#0af")
ap.Circle(x=125, y=75, radius=25, fill_color="#0af")
ap.display_on_colaboratory(html_file_name="apysc_example_1.html")

image.png

本記事では主題とずれるので詳しくは割愛しますが、他にもHTMLやjsで出力したり、Jupyter notebookやJupyterLabなどでも表示することは現時点できるようになっています。ただしVS Code上のJupyterはまだサポートしていません(挙動が大分異なるので色々と対応が難しそうな印象がしています)。その辺りはドキュメントのリンクだけ貼っておきます。

HTMLで出力したい場合:

Jupyter notebookやJupyterLab上で表示したい場合:

importの慣習

Pandasのimport pandas as pdといった記述やNumPyのimport numpy as np、Tkinterのimport tkinter as tkといったようなimportの仕方と同じように、apyscの本ライブラリでもimport apysc as apというimportを基本としており、そちらのパッケージパスで必要なインターフェイスを集約しています。本記事も同じような形で進めていきます。

追加したパス関係の各クラスやインターフェイスの概要

詳細は以降の節で少しずつ触れていきますが、今回追加したパス関係の各クラスやインターフェイスは以下のようになっています。

クラス名などは英語的にはPathよりもHorizontalとかを先にした方が自然なのですが補完のリスト表示でパス関係がまとまって表示されるようにしておきたかったため悩んだ結果決めています。

  • Pathクラス : 基本となるパスのクラスです。直接コンストラクタを読んだり各描画処理のインターフェイスでインスタンスが作成され、パスの描画が追加されます。引数で以下のPathMoveToPathBezier2Dなどの値を指定することで任意のパスを描画できます。
  • PathMoveToクラス : 特定の座標にパスの位置を移動させるための設定用のクラスです。他のクラスも同様ですが、Pathクラスのコンストラクタなどで指定します。
  • PathLineToクラス : 現在設定されている位置から特定の座標に対して線を描画します。
  • PathHorizontalクラス : 現在設定されている位置から特定の座標に対して水平方向の線を描画します。PathLineToクラスで垂直方向の設定が無くなってシンプルな引数になっています。
  • PathVerticalクラス : 現在設定されている位置から特定の座標に対して垂直方向の線を描画します。PathLineToクラスで水平方向の設定が無くなってシンプルな引数になっています。
  • PathCloseクラス : パスが閉じていない場合にパスの終点と始点を繋げる設定を追加します。
  • PathBezier2Dクラス : 現在設定されている位置から特定の座標に対して、指定された1つの制御点を使用して2次ベジェ曲線を描画します。
  • PathBezier2DContinualクラス : 直前で設定されている2次ベジェ曲線に指定された制御点の対角位置の制御点を使用し特定の座標に対して2次ベジェ曲線を描画します。直前のベジェ曲線から滑らかな曲線を繋げることができます。
  • PathBezier3Dクラス : 現在設定されている位置から特定の座標に対して、指定された2つの制御点を使用して3次ベジェ曲線を描画します。
  • PathBezier3DContinualクラス : 直前で設定されている3次ベジェ曲線に指定された制御点の対角位置の制御点を使用し特定の座標に対して3次ベジェ曲線を描画します。直前のベジェ曲線から滑らかな曲線を繋げることができます。
  • Graphicsクラスのdraw_pathメソッド : 設定されている塗りや線の色を保持したまま特定のパスを描画します。

コードにしてみると以下のような感じでパスの描画が行えます(詳細は後々の節でそれぞれ触れていきます)。

import apysc as ap

ap.Stage(stage_width=450, stage_height=125, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=50),
        ap.PathLineTo(x=100, y=25),
        ap.PathLineTo(x=150, y=50),
        ap.PathHorizontal(x=200),
        ap.PathVertical(y=75),
        ap.PathBezier2D(
            control_x=250,
            control_y=25,
            dest_x=300,
            dest_y=75,
        ),
        ap.PathBezier2DContinual(x=400, y=75),
    ],
    line_color="#0af",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_2.html")

image.png

Pathクラス

Pathクラスはパスの基本となるSVG描画を扱うクラスです。

コンストラクタのpath_data_list引数に各パス設定を追加することでその設定に応じたパスを描画します。

また、apyscの他の図形のクラスと同様に塗りや線のスタイル設定もコンストラクタの引数に指定することができます(例 : fill_colorやline_color、line_thickness引数など)。

import apysc as ap

ap.Stage(stage_width=200, stage_height=150, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=50),
        ap.PathHorizontal(x=100),
        ap.PathVertical(y=100),
        ap.PathHorizontal(x=150),
    ],
    line_color="#0af",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_path_1.html")

image.png

PathMoveToクラス

PathMoveToクラスは描画開始位置を指定した位置に移動させます。

対象のドキュメント:

例えば以下のコードではPathMoveToクラスでx=50, y=50の位置に描画開始位置を移動させています。そこから別のパス設定でx=100の位置に向かって線を描画しています。

import apysc as ap

ap.Stage(stage_width=150, stage_height=100, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=50),
        ap.PathHorizontal(x=100),
    ],
    line_color="#0af",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_move_to_1.html")

image.png

なお、現在のバージョンではpath_data_list引数の先頭にPathMoveToクラスを指定していない場合にはエラーになるようにしています。将来アップデートで指定していなかった場合にはX=0, Y=0になるように調整するかもしれません。

import apysc as ap

ap.Stage(stage_width=150, stage_height=100, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathHorizontal(x=100),
    ],
    line_color="#0af",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_move_to_2.html")
ValueError: `path_data_list` argument's first value can only accept a `PathMoveTo` instance. Actual: <class 'apysc._geom.path_horizontal.PathHorizontal'>

PathLineToクラス

PathLineToクラスは現在の位置から指定した位置に向かって線を描画します。X座標とY座標両方の指定が必要になります。

対象のドキュメント:

以下の例ではX=50, Y=50の位置からX=100、Y=100の位置に向けて斜めに線を描画しています。

import apysc as ap

ap.Stage(stage_width=150, stage_height=150, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=50),
        ap.PathLineTo(x=100, y=100),
    ],
    line_color="#0af",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_line_to_1.html")

image.png

PathHorizontalクラス

PathHorizontalクラスは現在の位置から水平方向に向けて指定された位置に線を描画します。
PathLineToクラスと似たような挙動をしますが、こちらはyの引数が無い形になっています。単純な水平方向の移動であればこちらの方が記述がシンプルになります。

対象のドキュメント:

import apysc as ap

ap.Stage(stage_width=150, stage_height=100, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=50),
        ap.PathHorizontal(x=100),
    ],
    line_color="#0af",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_horizontal_1.html")

image.png

PathVerticalクラス

PathVerticalクラスは現在の位置から垂直方向に向けて指定された位置に線を描画します。PathHorizontalクラスの垂直方向版です。

対象のドキュメント:

import apysc as ap

ap.Stage(stage_width=100, stage_height=150, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=50),
        ap.PathVertical(y=100),
    ],
    line_color="#0af",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_vertical_1.html")

image.png

PathCloseクラス

PathCloseクラスはもしもパスの終点と始点が繋がっていない場合に終点と始点を繋げる線を描画します。

対象のドキュメント:

例として以下のようなコードがあったとします。終点と始点は繋がっていません。

import apysc as ap

ap.Stage(stage_width=150, stage_height=150, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=50),
        ap.PathVertical(y=100),
        ap.PathHorizontal(x=100),
    ],
    line_color="#0af",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_close_1.html")

image.png

上記のコードのpath_data_list引数の最後にPathCloseクラスの設定を追加してみると終点と始点の接続がされて三角形になることが確認できます。

import apysc as ap

ap.Stage(stage_width=150, stage_height=150, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=50),
        ap.PathVertical(y=100),
        ap.PathHorizontal(x=100),
        ap.PathClose(),
    ],
    line_color="#0af",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_close_1.html")

image.png

PathBezier2Dクラス

PathBezier2Dクラスは制御点が1つの2次ベジェ曲線を描画します。引数のcontrol_xは制御点のX座標、control_yは制御点のY座標、dest_xは終点のX座標、dest_yは終点のY座標となります。

対象のドキュメント:

import apysc as ap

ap.Stage(stage_width=200, stage_height=150, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=100),
        ap.PathBezier2D(
            control_x=100,
            control_y=25,
            dest_x=150,
            dest_y=100,
        ),
    ],
    line_color="#0af",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_bezier_2d_1.html")

image.png

制御点(control_xとcontrol_yの位置)をシアンの円で可視化してみると以下のようになります。

import apysc as ap

ap.Stage(stage_width=200, stage_height=150, background_color="#555")

CONTROL_X: int = 100
CONTROL_Y: int = 25
path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=100),
        ap.PathBezier2D(
            control_x=CONTROL_X,
            control_y=CONTROL_Y,
            dest_x=150,
            dest_y=100,
        ),
    ],
    line_color="#fff",
    line_thickness=5,
)
circle: ap.Circle = ap.Circle(
    x=CONTROL_X,
    y=CONTROL_Y,
    radius=7,
    fill_color="#0af",
)

ap.display_on_colaboratory(html_file_name="apysc_example_bezier_2d_2.html")

image.png

PathBezier2DContinualクラス

PathBezier2DContinualクラスはPathBezier2Dクラス直後に指定することで、PathBezier2Dクラスの制御点の対角の位置の制御点を使用した曲線を繋げることができます。これによって滑らかな2次ベジェ曲線を描画することができます。引数は終点のxとyのみです。

対象のドキュメント:

import apysc as ap

ap.Stage(stage_width=300, stage_height=200, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=100),
        ap.PathBezier2D(
            control_x=100,
            control_y=25,
            dest_x=150,
            dest_y=100,
        ),
        ap.PathBezier2DContinual(
            x=250,
            y=100,
        ),
    ],
    line_color="#0af",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_bezier_2d_continual_1.html")

image.png

なお、このクラスはPathBezier2DクラスもしくはPathBezier2DContinualの後に指定されていない場合にはエラーになるようにしてあります。

import apysc as ap

ap.Stage(stage_width=300, stage_height=200, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=100),
        ap.PathBezier2DContinual(
            x=250,
            y=100,
        ),
    ],
    line_color="#0af",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_bezier_2d_continual_2.html")
ValueError: `PathBezier2DContinual` instance in a `path_data_list` argument can set only after a `PathBezier2D` or `PathBezier2DContinual` one.
Actual: <class 'apysc._geom.path_move_to.PathMoveTo'>
Index: 1

PathBezier3Dクラス

PathBezier3Dクラスは制御点が2つの3次ベジェ曲線を描画できます。control_x1とcontrol_y1は1つ目の制御点、control_x2とcontrol_y2は2つ目の制御点、dest_xとdest_yは終点の座標指定となります。

対象のドキュメント:

import apysc as ap

ap.Stage(stage_width=200, stage_height=200, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=150),
        ap.PathBezier3D(
            control_x1=25,
            control_y1=15,
            control_x2=175,
            control_y2=15,
            dest_x=150,
            dest_y=150,
        ),
    ],
    line_color="#0af",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_bezier_3d_1.html")

image.png

1つ目の制御点をシアンの円、2つ目の制御点をマゼンタの円として可視化すると以下のようになります。

import apysc as ap

ap.Stage(stage_width=200, stage_height=200, background_color="#555")

CONTROL_X1: int = 25
CONTROL_Y1: int = 15
CONTROL_X2: int = 175
CONTROL_Y2: int = 15

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=150),
        ap.PathBezier3D(
            control_x1=CONTROL_X1,
            control_y1=CONTROL_Y1,
            control_x2=CONTROL_X2,
            control_y2=CONTROL_Y2,
            dest_x=150,
            dest_y=150,
        ),
    ],
    line_color="#fff",
    line_thickness=5,
)

cyan_circle: ap.Circle = ap.Circle(
    x=CONTROL_X1,
    y=CONTROL_Y1,
    radius=7,
    fill_color="#0af",
)
magenta_circle: ap.Circle = ap.Circle(
    x=CONTROL_X2,
    y=CONTROL_Y2,
    radius=7,
    fill_color="#f0a",
)

ap.display_on_colaboratory(html_file_name="apysc_example_bezier_3d_2.html")

image.png

PathBezier3DContinualクラス

PathBezier3DContinualクラスは直前に設定されているPathBezier3Dクラスの制御点の対角座標の制御点を使用して3次ベジェ曲線を書き足します。PathBezier2DContinualクラスと似たような挙動をします。これを使うことによって滑らかな3次ベジェ曲線を描くことができます。

こちらのクラスでは1つ目の制御点はPathBezier3Dクラスの制御点の対角座標が使われますが、2つ目の制御点に関しては別途引数(control_xとcontrol_y)で指定する必要があります。

対象のドキュメント:

import apysc as ap

ap.Stage(stage_width=350, stage_height=300, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=150),
        ap.PathBezier3D(
            control_x1=50,
            control_y1=15,
            control_x2=150,
            control_y2=15,
            dest_x=150,
            dest_y=150,
        ),
        ap.PathBezier3DContinual(
            control_x=250,
            control_y=165,
            dest_x=300,
            dest_y=250,
        ),
    ],
    line_color="#0af",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_bezier_3d_continual_1.html")

image.png

Graphicsクラスのdraw_pathインターフェイス

他のクラスが持つメソッドとなりますがdraw_pathというインターフェイスも用意してあります。他の図形と同様に、こちらのインターフェイスは事前にbegin_fillやline_styleといった設定をしておくとdraw_系のインターフェイスを呼び出した際にそれらの塗りと線の設定が反映されます(描画の度に毎回塗りや線の設定などをしなくてもOKになります)。フォトショやイラレなどで事前に塗りや線の設定をしておいてから図形の描画などを行うのに近い感じになります。古の時代に神々の怒りに触れて滅びたAdobeのActionScript3という言語のインターフェイスを参考にしています。

対象のドキュメント:

import apysc as ap

ap.Stage(stage_width=300, stage_height=150, background_color="#555")

sprite: ap.Sprite = ap.Sprite()
sprite.graphics.line_style(color="#0af", thickness=5)
rectangle: ap.Rectangle = sprite.graphics.draw_rect(
    x=50,
    y=50,
    width=50,
    height=50,
)

# 線の設定をしなくともパスに関しても四角と同様のスタイルが反映されます。
path: ap.Path = sprite.graphics.draw_path(
    path_data_list=[
        ap.PathMoveTo(x=150, y=100),
        ap.PathBezier2D(
            control_x=200,
            control_y=15,
            dest_x=250,
            dest_y=100),
    ],
)

ap.display_on_colaboratory(html_file_name="apysc_example_draw_path.html")

image.png

相対座標での設定

各パス設定にはrelativeという真偽値の相対座標設定のオプション引数が存在します。デフォルトではFalse(絶対座標)となっています。

この値をTrueにすることで描画開始位置からの相対座標でパスを設定することができます。

以下のコード例では座標は全て50pxと指定していますが、相対座標指定をしているため段々とパスの位置が右下に移動していっていることが確認できます。

import apysc as ap

ap.Stage(stage_width=200, stage_height=200, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=50),
        ap.PathHorizontal(x=50, relative=True),
        ap.PathVertical(y=50, relative=True),
        ap.PathHorizontal(x=50, relative=True),
        ap.PathVertical(y=50, relative=True),
    ],
    line_color="#0af",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_relative_1.html")

image.png

線ではなく塗りを設定した場合

線ではなく塗りを設定した場合はフォトショやイラレなどでペンツールなどでベジェ曲線などを描画した時と同じようにパスの内側に塗りが設定されます。

import apysc as ap

ap.Stage(stage_width=300, stage_height=200, background_color="#555")

path: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=100),
        ap.PathBezier2D(
            control_x=100, control_y=15, dest_x=150, dest_y=100,
        ),
        ap.PathBezier2DContinual(x=250, y=100),
    ],
    fill_color="#0af",
    line_color="#fff",
    line_thickness=5,
)

ap.display_on_colaboratory(html_file_name="apysc_example_fill_color_1.html")

image.png

各属性の取得や更新

線や塗りの色や透明度・座標などはそのままプロパティにアクセスして更新などをすることができます。マウスイベントなどをトリガーに更新することも可能です。

以下の例ではパスのインスタンス生成後に線の透明度を低くしています。

import apysc as ap

ap.Stage(stage_width=150, stage_height=150, background_color="#555")

path_1: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=50, y=50),
        ap.PathLineTo(x=100, y=100),
    ],
    line_color="#fff",
    line_thickness=10,
)
path_1.line_alpha = ap.Number(0.3)

path_2: ap.Path = ap.Path(
    path_data_list=[
        ap.PathMoveTo(x=100, y=50),
        ap.PathLineTo(x=50, y=100),
    ],
    line_color="#fff",
    line_thickness=10,
)
path_2.line_alpha = ap.Number(0.3)

ap.display_on_colaboratory(html_file_name="apysc_example_property_1.html")

image.png

余談

趣味で作っていているのでスローペースな開発とはなりますが、もしも「良さそうじゃん」「今後に期待」と思われた方がいらっしゃいましたらGitHubのリポジトリにスターを残しておいていただけますと継続開発のモチベになって幸いです:bow:

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2