プログラミング
Lilにおけるベクターグラフィックス
Vector Graphics in Lil (beyondloom.com)
要約
この記事では、APLの影響を受けたスクリプト言語Lilにおける2次元ベクターグラフィックスの扱い方について解説しています。点、ストローク、パスといった基本要素の定義から始まり、Dr. Allen Vincent Hersheyのデジタル書体を基盤とするテキストパスの生成方法が示されます。さらに、拡大縮小、平行移動、せん断、回転といった様々なアフィン変換をLilの集合演算と独自のイディオムを用いて実現する方法が詳しく説明されています。
全文翻訳
Lilにおけるベクターグラフィックス
アレン・ヴィンセント・ハーシー博士は、単純な直線セグメントのシーケンスで記述される最初のデジタル書体のいくつかを開発しました。これらは元々、1967年の報告書『Calligraphy for Computers』で発表されました。Deckerエコシステム用のhersheyモジュールは、これらの書体を使用してテキストをレイアウトすることに関係しています。Deckerのスクリプト言語Lilは、珍しいAPLの影響を受けた設計をしています。この記事では、上記のインタラクティブなドキュメントでも議論されている例を拡張し、2次元ベクターグラフィックスの文脈でLilを探求します。
基盤
点はキャンバス(描画サーフェス)上の直交座標を記述する(x,y)の数値ペアです。点はLilでは長さ2のリストとして表現されます。
point:(3,-5)
ストロークは、キャンバス上に描画する一連の線を表す点のリストであり、したがって数値のリストのリストでもあります。4点のリストが与えられた場合、ストロークは最初の点を2番目の点に、2番目の点を3番目の点に、3番目の点を4番目の点に接続します。ストロークにN個の点がある場合、それはN-1本の接続された線を表します。ストロークを「ポリライン」と呼ぶこともできます。
stroke:((list 3,-5),(list 2,5),(list 0,3))
パスはストロークのリストであり、したがって点のリストのリストであり、数値のリストのリストのリストでもあります。パスは、文字「A」のように単一の形状を構成するストローク、または単語全体や文を表す一連の文字を表すことができます。パス内のストロークは不揃いであり、長さが異なる場合があります。
path:(list (list 1,2),(list 3,4)), (list (list 5,6),(list 7,8),(list 9,10))
hershey.textpath[]関数は、フォントのグリフ(それぞれがより単純なパスです)を使用して、Lil文字列に基づいて複雑なパスを組み立てます。
p:hershey.textpath[hf_futura "ABC"]
このように構築されたテキストパスは、明示的なeachループまたは同等のショートハンド@演算子を使用して、キャンバスcに描画できます。
each stroke in p c.line[stroke]end
c.line @ p
可能な限り、DeckerのスクリプトAPIは、一度に大きなデータ構造に適用される集合演算として機能するように設計されています。単一の直線セグメントを描画するのは、より一般的なポリライン描画操作の単純なケースです。もしcanvas.line[]が単一の直線セグメントしか描画できないとしたら、パスのN個の点とそれらが表すN-1本の線の間のオフバイワンの関係を明示的に管理しなければなりません。
例えば、
each stroke in p
each point i in stroke
if i>0 canvas.line[stroke[i-1] point] end
end
end
このような小さな人間工学的欠陥は、プログラム全体に現れると拡大され、プログラマの実際の意図の単純さを不明瞭にしてしまいます。1
パス操作
興味深いパスを取得してレンダリングできるようになったので、それらの操作を見てみましょう。パスの結合は簡単です。Lilの,演算子はリストを連結します。ストロークのリストとストロークのリストを連結すると、ストロークのリストが生成されます。パスのスケーリングはより興味深いものです。以前に議論したように、Lilの算術演算子の多くは、ネストされたリスト構造に対して暗黙的に適合します。スカラー数を数値のリストに乗算すると、乗算がリストの各要素に広がり、同じプロセスがネストされたリストに対しても再帰的に機能します。したがって、点、ストローク、またはパスを単純な乗算2で均一にスケーリングできます。
(11,22,33)*2# (22,44,66)
((list 3,-5),(list 2,5),(list 0,3))*2# ((6,-10),(4,10),(0,6))
非一様スケーリングは問題に直面します。パス内にネストされたすべての点のx座標をy座標とは異なる値で乗算したいとします。パスを数値のペアで乗算すると、*演算子は、その適合プロセスを最も内側のペアに「プッシュ」したいことを暗黙的に知りません。*が異なる長さのリストを左右に与えられた場合、右のリストは左のリストの長さに合わせて繰り返されるか、切り捨てられるかのように扱われます。
(1,2,3,4)*(10,100)# (10,200,30,400)
まず、右オペランドをlistで囲むと、*はその長さ1のリストに含まれる要素を繰り返し、左引数の各要素とペアにします。
(1,2,3,4)*list(10,100)# ((10,100),(20,200),(30,300),(40,400))
パスの場合、右オペランドを2回囲む必要があります。1回はスケールをパスの各ストロークに広げるため、もう1回はスケールをストロークの各点に広げるためです。
p * list list scalex,scaley
xまたはy軸を負の数でスケーリングすると、パスはそれぞれ水平または垂直にミラーリングされます。
p * list list -1,1
パスの平行移動はスケーリングと同じトリックを使用できますが、*の代わりに+を使用します。
p + list list transx,transy
水平方向にせん断するには、パス内のすべての点のx座標をy座標に比例してオフセットする必要があります。lastプリミティブを使用して、パス内のすべての点のy座標を抽出できます。@演算子を使用すると、lastを右オペランドに「プッシュ」できます。パスのlastはストロークです。各ストロークのlastは点のリストです。各パスのlastのlastはy座標のリストのリストです。
p: (list (list 1,2),(list 3,4)),(list (list 5,6),(list 7,8),(list 9,10))
last p# ((5,6),(7,8),(9,10))
last @ p# ((3,4),(9,10))
last @ @ p# ((2,4),(6,8,10))
この種の、パス内の点から派生したスカラーのリストのリストを「ピール (peel)」と呼びましょう。y座標のピールを適切なパスに戻すには、各スカラーをペアで乗算し、非一様スケーリングのイディオムを再利用できます。
(last @ @ p)*list list(10,0)# (((20,0),(40,0)),((60,0),(80,0),(100,0)))
したがって、完全な水平せん断には、
p + (last @ @ p)*list list shearx,0
垂直せん断は非常によく似ています。
p + (first @ @ p)*list list 0,sheary
パスを転置したい場合、x座標とy座標を別々にピールして、同じ種類のイディオムを使用してブレンドし直すことができますが、rev(反転)プリミティブを直接使用して各点の要素をスワップすることもできます。
rev @ @ p
転置だけではあまり役に立ちませんが、ミラーと転置を組み合わせると90度回転が生成されます。パスを回転させるより一般的な方法は、Lilのheading、mag、unitプリミティブを利用することです。magプリミティブは、数値または数値のリストのベクトル量を計算します。単一の数値の場合、これは絶対値です。点のようないくつかの数値のペアの場合、その点と原点との間のユークリッド距離を与えます。また、特別な適合動作も持っています。数値またはフラットな数値リストに到達するまで、ネストされたリストを再帰的に下降します。この動作は、パスからピールを生成するのに最適です。
p: (list (list 1,2),(list 3,4)),(list (list 5,6),(list 7,8),(list 9,10))
mag p# ((2.236068,5),(7.81025,10.630146,13.453624))
headingプリミティブはmagと同様に適合しますが、原点と各点との間の角度(ラジアン単位)のピールを生成します。
heading p# ((1.107149,0.927295),(0.876058,0.851966,0.837981))
magとheadingを一緒に使用すると、パス(直交座標)が極座標に分解されます。unitプリミティブは、headingから取得した角度のピールを変換し、すべての点が原点から単位距離(mag 1)にあるパスを生成します。任意のパスp3に対して、
p ~ (mag p)*unit heading p
したがって、原点を中心とした角度ラジアンでの完全なパス回転は、
(mag p)*unit angle+heading p
unitのようなプリミティブは、正多角形、円弧、楕円などの様々なストロークをループなしで構築する方法も提供します。結果をlistで囲むとパスが得られます。
on ngon sides radius do list radius*unit 2*pi*(range sides+1)/sidesend
これで、パスのアフィン変換に関するLilのイディオムのセットが完成しました。
複雑な効果:他に何ができるか?
原点を中心としたパスから始めると、パス内のすべての点をそれぞれのmagでスケーリングし、遠近法の歪みを生成できます。0から1の間で変化するtween値tと、適切に選択されたいくつかの定数が与えられた場合、
p*.3+(mag p)*.01*t
ランダムな[-1,1]オフセットのストロークを生成した場合、パス内のすべてのストロークにそれを追加することができます。