ベジエ曲線(ベジェ曲線)について

(WAKWAKに設置していたサイトからの転載(一部改訂)です。)



 Adobe Illustrator で描かれる曲線はベジエ曲線(3次ベジエ曲線)というもので、図のようにアンカー2点+ハンドル2点の計4点の座標を元にして描かれる曲線です。「ベジエ」とは、この曲線を自動車のCAD (Computer Aided Design) に応用した仏人 Pierre Bézier さんに由来するそうです。


 別に何次の何曲線だろうが、Illustrator を使うときには関係ないだろう、と思われる向きもあるかと思いますが、この曲線のいくつかの性質は、知っておくと役立つことがあると思います。例えば「ハンドルは必ずアンカーの位置での接線になる」、「曲線は必ず4点を結ぶ四角形の内側に描かれる」といったことがそれです。


 メニューから「アンカーポイントの追加」を実行したときに追加されるアンカーの位置はパスの中点とは限らず、気まぐれな位置に追加されてるような気もしますが、これもベジエ曲線の仕組みから考えれば、それなりに根拠のある位置なのだとわかります。


 だ円ツールで描いた円は、正確な円ではなく若干歪みがあるのですが、実際のところ、どのくらい歪んでいるのでしょうか。これもベジエ曲線を表す式から割り出すことができます。

数式で表すの巻


 図のようなアンカーとハンドルを持つベジエ曲線は、実際どのように描かれているんだろうか?という話です。コンピュータがやってることなので、適当に滑らかな線を描いているわけではなく、律儀な計算に基づいています。


 ベジエ曲線が描かれる仕組みを説明するために、「ド・カステリョのアルゴリズム」が、よく引き合いに出されます。これは以下のような考え方です。


 まず、ハンドルおよびハンドルの先端を結んだ線を、それぞれ、同じ比率で分割する点を取ります。次に、その点を結び、結んだ線を、再び同じ比率で分割します。さらに、その点を結び、結んだ線を同じ比率で分割すると、ベジエ曲線上の1点(P)が定まります。この比率を 0:1 から 1:0 まで、連続して変化させると、点 P の位置も連続して変化して、滑らかな曲線を描く、というカラクリです。


 ここで、0から1まで連続して変化する数 t を考え、分割比率を t : 1−t として分割点の座標を計算していくと、点 P の座標 (x(t), y(t)) は次の式で表されます。

 これが(3次)ベジエ曲線の式になります。2つの式で、上の図の1本の曲線を表しています。座標を決める元にした数 t はパラメータ(媒介変数)と呼ばれ、パラメータを使って表した曲線をパラメトリック曲線と呼びます。


 普通に Illustrator を使っていて、この式を解く機会は、ほとんどないと思いますが、たとえば、メニューから「アンカーポイントの追加」を実行した場合は、上の図で t = 0.5 (分割比率0.5:0.5)のときの点にアンカーが追加されるのだ、と考えると、なんとなく目安になるのではないでしょうか。


 ところで「3次ベジエ曲線」の「3次」ですが、上の図のように「割って割って割って」と3段階踏むので「3次」になります。TrueType フォントや Flash で使われている2次ベジエ曲線の場合は「割って割って」の2段階になります。2次ベジエ曲線ではアンカー2つに対してハンドル(制御点)は1つです。

円のハンドルの巻


 Illustrator の だ円ツールで円を描くと、アンカーが4つのパスが出来ます。このときのハンドルの長さの比率がわかっていると、スクリプトで円弧を描くときなんかに便利です。また、この比率は「角を丸くする」フィルタなどでも使われています。ハンドルの座標を知るには、適当に円を描いて保存したファイルの中のデータを覗く手もありますが、ここでは理屈で割り出してみます。


 この円のアンカー間の線(セグメント)を1つとってみると、ハンドルは左右対称形をしています。ハンドルが左右対称形の場合、パラメータ t = 0.5 のときの点は曲線の中点になります。これは、前回触れた何とかのアルゴリズムに照らしてみると直感的にも明らかでしょう。



 いま、原点を中心とした半径1の円を、だ円ツールで描いたとして、アンカーとハンドルの座標を図のように割り当ててみます。


 この曲線を円弧とするなら、パラメータ t = 0.5 のとき、曲線の中点( cos 45°、sin 45°)を通るようにするのが良さそうです。(ハンドルは左右対称形なので t = 0.5 のときに中点を通ります。)


 これらの事情を元にして、前回のベジエ曲線の x(t) の式に、パラメータ t と各点の x 座標を代入すると、半径1の円を描いたときのハンドルの座標の値 a は次のように求められます。

 なお、このようにハンドル長を決めたベジエ曲線は、あくまで円を近似したもので、正円と比べると若干の誤差があります。計算してみたところ、ベジエ曲線上の点と原点との距離は、約 1.00000 〜 1.00027 の範囲になりました。より誤差を少なくするような制御点の取り方もあるはずですが、弧の中点と中心を結んだ距離が半径と一致する、としたほうが、何かと便利な気もします。


 また、このハンドル長の値は、約 0.5522847 になり、Google 検索すると円関係の定数としていくつか Hit します(詳細不明)。Illustrator CS で原点を中心に半径 1000pt の円を描いて、保存したファイルの中身を見ると、552.2852 という座標値になっていました。



 任意の中心角の円弧の場合も、上記と同様にしてハンドルの長さを求められます。


 アンカーとハンドルの座標を左の図のように割り当てて計算すると、 a は以下のような値になります。

分割の巻

 下の図の P 点のところを ペンツールでポチッと突付いてアンカーを追加、というのは、言い換えればベジエ曲線を P 点で分割する、ということをしています。分割されたそれぞれの曲線は新たなハンドルを伴っていますが、このハンドルの位置がどう決まるのかの話です。何でこんなこと考えるのかというと、たとえばスクリプトでパスにアンカーを追加するときは、この新たなハンドルの座標を算出して適切に指定してやる必要があるのです。


 いかにも面倒そうですが、実は意外と単純です。



 右の図は、「 数式で表すの巻 」で触れた、 t :1−t で分割して曲線上の点を決める仕組みの図です。一部が赤くなっていますが、この赤い線が、曲線上の点で分割した場合の新しいハンドルになります。実にシンプルというか合理的です。


 つまりハンドルの位置を割り出したいときは、制御点の間を t :1−t で分割していけばいいわけです。0≦t≦1 のとき、2点の間を t :1−t で分割する点の座標は下の図のようになります。これより、図の点 R の x 座標も、下のように求められます。

※ 2017.06.24 数式を修正しました。

接線の巻

 ベジエ曲線上のある点での接線の傾きを割り出したい場合には、「 曲線といえども接点の部分で ものすごく拡大して取り出して見るとほとんど直線だから、その直線の傾きを接線の傾きとすればいいじゃないか 」というような考え方をします。具体的には「 微分 」を使います。ビブンと聞いて拒絶反応が出る方でも、右下の図のような手順で計算すると x'(t) とやらが出るとだけ考えれば何とかなると思います。


「 数式で表すの巻 」で書いたベジエ曲線の式を、右のようなかんじで t について微分したものを x'(t) 、 y'(t) とすると、

が、パラメータ t のときの曲線上の点での接線の傾きになります(分母が0のときの処理に注意)。


 逆に、傾き s の直線が接する曲線上の点は、

を t について解いて、その パラメータ t から曲線上の点を求めることで割り出せます。

長さの巻

 ベジエ曲線の長さを割り出すには「 積分 」を使います。


 上で接線の傾きを割り出す際には、曲線を細かい直線に分けましたが、その細かく分けたものを全部つなげると長さが割り出せるじゃないか、という考え方です。



 公式としては、右のようになります。底辺 x'(t) 、高さ y'(t) の極小三角形の斜辺の長さを求めて、それを全部足すというようなことをしています。


 公式から実際に長さを算出するには、数値積分の手法(台形法、シンプソン法、など)を用います。

ベジエ曲線の資料

 Don Lancaster's Guru's Lair Cubic Spline Library には、ベジエ曲線がらみの資料がたくさんあります。英語+数式+PDFという三重苦に耐えて読みますと、いろいろタメになりそうな雰囲気です。


 そういう私は、まだ ほとんど読んでないんですけど。中に、サインカーブの近似 という資料があったので、それを参考にしてサインカーブを描く JavaScript スクリプトを作ってみました。実行すると原点(デフォルトではアートボードの左下)付近に描きます。ちなみに数式部分をすっとばして結果の座標だけ拝借したため、どうやってどの程度 近似しているのかはわかりません。でもソレっぽいと思います。(※1)

download:→ http://shspage.com/aijs/


※1:〔追記〕これでは心もとないので、実際の sin の値による折れ線( x 座標の間隔= PI/100 )と重ねてみました。拡大すると若干のズレも確認できますが。ちょっとした図版に使うには十分に実用的ではないでしょうか。> sine_cmp.pdf


 以前「 数式で表すの巻 」で、特定のパラメータに対して曲線上の1点が決まる仕組みとして触れた「ド・カステリョのアルゴリズム」については、国内の複数のサイトを参考にしましたが、このサイトの "Bezier curve recursive midpoint rule"(ベジエ曲線 再帰中点法?)という資料で紹介されている方法は、それとは少し違っていて、制御点間の中点をどこまでもハンブンコハンブンコして曲線上の点を次々と導き出すということをしているようです。この描き方は 1959 年にポール・ド・カステリョが発見し、1960 年代にピエール・ベジエが CAD に応用したのだそうです。その簡潔さに基づく美しさから、本来の「ド・カステリョのアルゴリズム」は、こっちなんじゃないかと思わされますが、詳細はまだわかりません。


つづく。

パスの等間隔分割、放物線のグラフを描く、円すいを描く

WAKWAKに設置していたサイトを独自ドメインサイトに移転することにしました。
記事系のものはこちらに転載していこうと思います。(元記事:Ai-Tips 2007/10/28)

  • パスの等間隔分割
  • 放物線のグラフを描く
  • 円すいを描く

パスの等間隔分割

【fig.1】 ジグザグフィルタで、大きさを0にすると、アンカー間を(折り返し+1)に等分割できるようだ、ってことに最近気がついて、そういえば、…

【fig.2】 … ブレンドで軸を曲線で置き換えると、等間隔にならなくて、もどかしかったりしてたのが、これで何とかなるのかな? と、やってみたら、何とかなったみたい。とりあえず見た目では、だいたい等間隔ですよね。

【fig.3】 では、普通に置き換えたときの間隔は何なのかというと、ベジエ曲線のパラメータ( 詳細は「 ベジエ曲線 」のページを参照ください )を等分割してるようなのでした。fig.3 は、置き換えた軸の上に同じパスを重ね、スクリプトを使ってパラメータ基準で分割したもので、軸自体を分割すると、ブレンドされたオブジェクトの位置が動いてしまいます。

放物線のグラフを描く

y = x^2 のグラフの描き方です。

【描き方1】
  1. 正方形を9つ並べたガイドを作り、図のようにパスを描きます。
  2. 左下をグラフの原点に合わせて、しかるべき座標を通るように拡大・縮小します。このときアンカーやハンドルを個別に動かさないようにして下さい。
  3. 複製・反転して、反対側を作ります。


  y = x^3 の場合は、手順 1 で右→の図のようにパスを描きます。

【描き方2】

 下↓の図のように正方形を3つ並べたガイドを作り、パスを描きます。描いたあとに「アンカーポイントの追加」を実行しておくと、グラフの原点に合わせるときに便利だと思います。


【描き方1の検証】

 ベジエ曲線の式を、パラメータ t について整理すると、上の図の上の式のようになります。
この式が x ( t ) = t,\/ y ( t ) = t^2 になるように各座標( x_n,\/ y_n )を決めてやれば、その曲線は y = x^2 に他なりません。 y = x^3 の場合も同様です。

 座標を決めて描いてみた曲線が、上の図の下のものです。左が y = x^2 、右が y = x^3 です。

円すいを描く

 一番の問題は、頂点から、底面の だ円への接線をどうやって描くかということです。

任意の点から だ円への接線を描く

 まず、上の図で直角の印をつけた部分は 必ず直角になることを思い出しておいてください。

 このことから下の図の 左 のものが描けます。
この図を紙に印刷して、ナナメから見ると、右 のようになります。以上が基本となる理屈です。

 実際の描き方としては下のようになります。

  1. 「任意の点」と、だ円の中心を結ぶ。
  2. その線に「アンカーポイントの追加」で中点を追加する(次の作業をやりやすくするため)。
  3. だ円を複製して、線の両端が周上に来るように、縦横の比率を変えないように変倍する。
  4. だ円の交点と「任意の点」を結ぶ。


円すいを描く

 上の手順をもとにして、以下のような手順で円すいが描けます。

  1. 底面となるだ円を描く。
  2. だ円を複製して拡大し、下のアンカーを最初のだ円の中心の位置に重ねる。
  3. だ円の交点と 上のアンカーを結ぶ。

batchTextEdit.jsx

Illustratorでポイントテキストをいちいちクリックしながら編集するのって、とても面倒。
一気に編集してしまいたい、という時に使える日常業務補助系スクリプトを書きました。
CS3とCS6のWindows版で動作確認しています。

使い方:
1.テキストオブジェクトを選択して、スクリプトを実行します。ポイントテキストだけでなく、エリアテキストやパステキストが混ざっててもOKです。テキストオブジェクト以外は無視するので一緒に選択されていても構いません。
2.ダイアログでテキストを編集します。内容をいったんエディタにコピーして、編集してからペーストしてもよいかもしれません。終わったらOKを押すと反映されます。

注意:

  • 編集結果を反映する際に、元のテキストにいろいろな書式が設定してあってもまったく考慮されません。1文字目の書式が全体に適用されます。
  • テキストオブジェクトが複数行の場合、改行文字は「@/」に変換されて表示されます。編集結果を反映する際に、「@/」が改行文字に戻されます。テキストを編集する際に、改行を使いたい場合もこの文字列を入れてください。逆に、「@/」という文字列自体は使えないことになります。それでは困るという場合、スクリプト内の設定を変えてください。
  • ダイアログに表示されるテキストの順番は、選択範囲(選択中のテキストオブジェクトの左上の点を囲む四角)の幅と高さによって決めています。高さのほうが大きい場合、上から下の順番です。上下位置が同じ場合は左が優先されます。幅が大きい場合は左から右で、左位置が同じ場合は上が優先されます。
  • 元のテキストオブジェクトの数より、編集後の行数が多い場合、多い部分は無視されます。少ない場合は、対応する行がないテキストオブジェクトは変更されません。

githubに置きました。
github.com/shspage/illustrator-scripts

Forkして貴方仕様に改造するのも大歓迎です。こう、1つのリポジトリに詰め合わせみたいにすると扱いにくいかもしれませんが、小さいスクリプトだしね。ライセンスファイルも1箇所に置いといたほうが気が楽というか。

メモ:
multilineのedittext内に改行が入力できないでつまづきました。調べたらFAQっぽくて、Ctrl+ENTERで入る、CS6ではwantReturnという追加された属性を指定すれば普通にENTERで入るとのこと。

追記 (2013.01.27 15:34) グループ化されたオブジェクトの扱いをミスっていました。修正いたしました。

ovalize.jsx

以前、掲載した Circle.js という Illustrator スクリプトがあって、指定したアンカー数で円を作るというものなのですが、作ったあとで大きさとか塗りや線幅を変えないといけませんでした。

指定したアンカー数の円が得られるというのが大事なので、それくらいの手間はそれはそれで大した問題ではなかったのですが、やっぱり手間は手間なので、少し改造してみました。

選択したパスを、それぞれの幅と高さに収まる楕円に変えます。
望みのサイズや色で描いた円を選択して、スクリプトを実行し、アンカー数をプロンプトで指定してやると、そのアンカー数の円になる、という使い方を想定しています。

が、スクリプトは「元のパスの幅と高さに収まる楕円を描く」としか考えてないので、選択したパスが円でなくても、三角でも四角でも直線でも、ぜんぶ楕円に変えるというワイルドな仕様にしています。
これはこれで使い道がありそうな気がします。

元のパスのアンカーポイントの数と位置を変えているだけなので、塗りの色や線幅などの属性は維持されます。

上の3番目のケースは、傾いた楕円のアンカー数だけ変えたいところなのですが、結果は「幅と高さに収まる楕円」になってしまいます。
円のアンカー数を変えるのがスクリプトの本来の目的なので、これは仕様がない仕様とします。

スクリプトは github で公開しております。

https://github.com/shspage/illustrator-scripts

今後このリポジトリにいろいろ追加していこうと思います。

Tree-likeUI.js と jsLinb UI Builder hack

Christopher Greenさんが、拙作「木のようなものを描く」スクリプトにUIをつけてくれました。
そう、以前にも、つけてくれた方がいるのですが、Greenさん版の面白いところは、UIを作るのにjsLinbというWebアプリケーション用のフレームワークのGUIビルダーを使っているところです! これは少しの下準備で、ダイアログの作成が本当に簡単に行える優れものです。


なにはともあれ、こちらが送っていただいたスクリプトです。
Tree-likeUI.zip -> http://shspage.com/ex/files.html


あと、スクリプトの実行オプションを再利用できる仕組みが取り入れられています。具体的には、作成した木(グループ)に、実行オプションの値を列記した名前がついていて、この名前のついた木を選択した状態でもう一度スクリプトを実行すると、その値が既定値になってダイアログが開くというわけです。


さて、GUIビルダーの話ですが、ただしjsLinbのものがそのまま使えるわけではなく、ちょっとした下準備が要ります。
以下が、ダイアログをイラレで表示するまでの手順です。

  1. Bethosのページに行きます。BethosはGreenさんが作成したjsLinb UI Builderの機能拡張です。
  2. ページの下の方のGo to the codeからJavaScriptのコードのページに行きます。
  3. 移動先でページ全体のテキストをコピーします。
  4. jsLinb UI Builderに行きます。
  5. ウィンドウ左上のCodeタブをクリックし、コードをすべて選択、さきほどコピーしたコードに置き換えます。
  6. Design Viewに戻ると、図のような画面になります。
  7. 右上のScript Windowというタイトルのあるダイアログが、これから編集するものです。赤い説明文を選択して、右クリックで Delete を選んで削除します。
  8. 左のTool Boxのすべてが使えるわけではありません。使える項目と、ScriptUIの何に対応するかが、ダイアログの左にあるQuick Referenceに書いてあります。普通の項目ではなく、Advanced 〜を使うものが多いですね。Tool Box からダイアログの上にドラッグして配置していきます。
  9. Advanced CheckBoxは、checkboxとradiobuttonに対応しており、themeにradioと入れることでradiobuttonになります。Bethosのページの、Q: "What potential problems should I be aware of?" に、その他の注意が書いてありますのでご一読ください。
  10. ページ右上の大きい緑色の矢印をクリックすると、プレビューページが開きます。
  11. dialogを選んで(その他はまだ試していません)、プレビューされているダイアログの右上の×をクリックすると、CSで使えるJavaScriptコードが表示されます。
  12. これを拡張子jsのファイルに保存し、スクリプトとして実行すると、図のようなダイアログになります。(WindowsXP/Illustrator CS5)


ダイアログから値を取得するには、ScriptUIの知識が少し要ります。win.checkbox1.value, win.textedit1.text みたいな感じです。
私はScriptUI for dummies | Peter KahrelのPDFをアンチョコにして勉強中です。他にわかりやすいものがあったら、ぜひ教えてください!

paper.js で筆圧描画とSVGデータ生成(2)

前回のものに、カラーピッカーと線幅スライダーと、アンドゥ・リドゥ機能をつけてみました。


paper.jsで筆圧描画とSVGデータ生成サンプル(2)
※ 私の環境のInternet Exprolerでは相変わらず使えてません。


カラーピッカーはこちらのもの。これで使っているライブラリ script.aculo.us のサンプルにスライダーがあったので、線幅調整用に使用。同じく使っている prototype.js の機能(クラスの継承)をアンドゥ・リドゥに使用しました。


アンドゥ・リドゥは修飾キーなしの z、shift-z で機能し、描画だけを対象としています。色や線幅の変更は取り消しできません。仕組みとしては、アンドゥは最後に描いた線を見えなく(visible=false)しているだけ。リドゥは再び見えるようにしているだけ、というとりあえずの実装です。アンドゥ後に描画すると、リドゥはリセットされます。(見えない状態になっている線が消去される。)


この仕様だと、リドゥ可能な状態でSVGの書き出しをすると、visible==false な線も書き出されてしまうので、visible==false のItemは書き出さないように、書き出しスクリプトを変更しました。書き出すかどうかについては、スクリプト内に設定値があります。


あとはレイヤーがあれば、イラスト描きツールとして実用になりそう。
レイヤー機能は paper.js に元々あるので、インターフェイスが作れれば、これもそれほど手間をかけずに実現できそうです。