kenban nikki    archives   tags   

DrawBotでベジェ曲線を描いてみる

2019-03-20

ベジェ曲線についてしっかり理解するためにDrawBotを使って描いてみます。DrawBotについてもう少し知りたい方はこちらをどうぞ。

ベジェ曲線 (Bézier curve)

ベジェ曲線はAdobe Illustratorなどのベクターグラフィックスエディタで絵を描く時によく使われている曲線です。文字のアウトラインを作る時にもベジェ曲線がよく使われます(クロソイド曲線も有望のようですが歴史的にはベジェ曲線一強です)。その名前は1960年代にPierre Bézierというルノーのエンジニアがこの曲線を考案したことに由来しています1

ベジェ曲線のメリットは軽いことと滑らかなことです。N個の点でN-1次の曲線を表現することができるので保存時に必要なデータはN個の点に関する情報だけです。また、写真などのビットマップ画像は拡大すると粗くなっていきますが、ベジェ曲線を使って描画されるベクター画像はいくら拡大しても滑らかです。点の情報に基づいてその都度コンピュータが曲線を計算しているためです。

更に、ベジェ曲線はそこそこ直感的に操作できます。グラフィックスエディタで使われている三次ベジェ曲線までなら練習をすればかなり自由に扱えるようになります。

フォントの文脈から言うとTrueTypeには二次ベジェ曲線(制御点が3つ)が使われていて、PostScript、METAFONTやSVGには三次ベジェ曲線(制御点が4つ)が使われています。OpenTypeはこれらのアウトライン情報を他の情報(メタデータ、スペーシング、カーニングなど)と一緒にラップアップしたものなのでどちらが使われている可能性もあります。

「TrueTypeには二次B-スプライン曲線が使われている」という説明をたまに見かけますが、「正しいかもしれないし正しくないかもしれない」というのが実情です。例えば有名な鹿本2には「B-splines (‘B’ as in “Bézier”)」(p. 961)との記述がありますが、これはどうも正しくないようです。というのも、B-スプラインのBはbasisのB3だからです4。ただ、ベジェ曲線は実はB-スプライン曲線(basis spline curve)の特殊ケースです。ここでは詳しく述べませんが、B-スプライン曲線に一定の制約を加えるとベジェ曲線になります。この制約の部分を意識的にあるいは無意識に省略していると理解するならば前述の説明も間違っているとは言えないため、「正しいかもしれないし正しくないかもしれない」のです。フォントの専門家たちが5年くらい前にTwitter上でこれについて議論をしていたようで、ここにまとめられています。

という解説はここまでにして、とりあえず描いてみましょう。

二次ベジェ曲線 (Quadratic Bézier curve)

まずはTrueTypeフォントに使われている二次ベジェ曲線です。3つの制御点を$P_{0}$$P_{1}$$P_{2}$とすると、ベジェ曲線上の点$P(t)$は以下の公式で求められます。

$$ P(t) = (1-t)^2P_{0} + 2(1-t)tP_{1} + t^2P_{2}, t \in [0, 1] $$

このうち、$P(t)$$P_{0}$$P_{1}$$P_{2}$は点の座標で$t$は0–1の値をとるパラメータです。$t$を0から1まで動かした時に得られる点$P(t)$の集合が曲線となるわけです。

それではDrawBotを使って順番を追って描いていきます。まずはキャンバスを作り、適当に定義した$P_{0}$$P_{1}$$P_{2}$を表示します。

点をNumPy配列として定義するとベクトル演算ができる(つまりxy座標をまとめて計算できる)のでとても便利です。ただ、NumPyはDrawBotにバンドルされていないのでまずは外部パッケージをインポートできるようにしてください。

さて、制御点を描いてみるとこのようになります。コードはこちらです。ストレートフォワードですね。

次に、線分$P_{0}P_{1}$と線分$P_{1}P_{2}$上でそれぞれ$t$に従って動く点$P_{01}$$P_{12}$を求め、表示します。以下の式によって求まります。

$$ P_{01}(t) = P_{0} + t(P_{1}-P_{0}), t \in [0, 1] $$ $$ P_{12}(t) = P_{1} + t(P_{1}-P_{2}), t \in [0, 1] $$

上の図では$t \in \{0.2, 0.4, 0.6, 0.8\}$の時を求めてみました。コードはこちらです。

次に、先程求めた点からなる線分$P_{01}P_{12}$上で$t$に従って動く点$P(t)$を求めます。この点の集合がベジェ曲線です。$t=0$$t=1$の時の結果も追加しました。この画像では曲線に見えませんが実際には$t$は連続変数なので滑らかな曲線になります。

この時の式は下記の通りです。下記式に上の$P_{01}(t)$$P_{12}(t)$の式を当てはめて整理すると、(なんと)一番上に示した二次ベジェ曲線の公式になります。

$$ P(t) = P_{01}(t) + t(P_{12}(t)-P_{01}(t)), t \in [0, 1] $$

これで二次ベジェ曲線を描けたことにして、動画を作っておしまいにしたいと思います。コードはこちらです。

三次ベジェ曲線 (Cubic Bézier curve)

次に三次ベジェ曲線です。4つの制御点を$P_{0}$$P_{1}$$P_{2}$$P_{3}$とすると、公式は以下のようになります。二次ととてもよく似ていますね5

$$ P(t) = (1-t)^3P_{0} + 3(1-t)^2tP_{1} + 3(1-t)t^2P_{2} + t^3P_{3}, t \in [0, 1] $$

二次ベジェ曲線が描けたのなら三次も簡単に描けます。ステップが一つ多くなるだけです。サクサクいきましょう。

まずは定義した制御点を描きます。

次に制御点からなる線分上で$t$に従って動く点を描き加えていきます。先程と同様に$t \in \{0.2, 0.4, 0.6, 0.8\}$の時を求めてみましょう。先程と違う点は線分が3本あることです。

次に、ここで得られた新たな線分上で$t$に従って動く点を描き加えます。ちょっと妙な感じですが、これで合っているはずです。

最後にこれらの線分上で動くベジェ曲線の集合となる点$P(t)$を求めます。

これで三次ベジェ曲線も描けました。動画も作ってみます。ここまでのコードはすべてこちらからどうぞ。

実はこの曲線、先程の二次ベジェ曲線と全く同じ形です。それは4つの制御点を適当に定義したわけではないからです。詳しくはまた今度、二次ベジェ曲線と三次ベジェ曲線の相互変換について書く時にとっておきます。

それはともかく、この2つの例からベジェ曲線の次の性質がなんとなく分かります。

  1. 両端にある制御点(on-curve points/アンカー)を必ず通るが、中間の制御点(off-curve points)を必ずしも通るとは限らない
  2. 制御点によって作られる多角形に内包される(凸包性という言うらしい)
  3. 隣り合うon-curve pointとoff-curve pointからなる線分(ハンドル)は曲線の接線である

他にもアフィン不変性などいくつかの性質があります。

もっと複雑なかたち

さて、曲線が高次になるにしたがって一本の線で表現できる形はより複雑になっていくのですが、実際には三次までのベジェ曲線を複数つなげてより複雑なかたちを作るのが一般的です。

その際、隣り合う曲線(セグメント)は一つの制御点を共有しているのですが、そこから出るハンドルが同一直線上に存在するとある程度スムーズなつなぎ目ができます。機会があったら描いて確かめてみます。

おわりに

つらつらと書いたり描いたりしていたらだいぶ長くなってしまいました。ここで書こうと思っていた二次ベジェ曲線と三次ベジェ曲線の相互変換に関しては別の記事で書きます。また、B-スプライン曲線やクロソイド曲線についても書いてみたいと思います。

ベジェ曲線についてもっと知りたい方は以下のリンクからどうぞ。この記事の参考にもさせていただきました。


  1. それに先立ちシトロエンのエンジニアであったPaul de Casteljauが理論と数式を発表していましたが、公知とはならなかったようです。 ↩︎

  2. Haralambous, Yannis. Fonts & Encodings. O'Reilly, 2007. ↩︎

  3. https://en.wikipedia.org/wiki/B-spline ↩︎

  4. ベジェ曲線のことをBézier curveではなくBézier splineと表現することはあるようですが、B-splineと略すのは誤解を招くので止めたほうがよいかもしれません。 ↩︎

  5. ベジェ曲線の各項の係数はバーンスタイン基底関数で表すことができます。 ↩︎