図形の一部を出力したくない、あるいは切り取りたいときに使う(PLRM 4.4.2)。 たとえば四角い枠いっぱいに太目の線を斜めに引くことを考える。 線の端は設定に応じて四角くなったり丸くなったりするが、枠いっぱいに書けば枠からはみ出すだろう。 これを枠ぴったりに収めたいときなどに便利である。
/figure1 { 100 0 rlineto 0 100 rlineto -100 0 rlineto closepath } def 100 100 moveto figure1 1 setlinewidth stroke 100 100 moveto figure1 clip newpath 100 100 moveto 100 100 rlineto 10 setlinewidth stroke
左: クリッピングなし、右: クリッピングあり
クリッピングパスはclipオペレータで設定する。 すでにクリッピングパスが設定されている場合、そのクリッピングパスと、これから設定するクリッピングパスのいずれでも描画される部分が新たなクリッピングパスとして設定される。 つまり、クリッピングパスは現在のクリッピングパスと同じか、より狭くなる。 initclipしない限りクリッピングパスを広げることはできない。 クリッピングパスはグラフィックステートに保持されるので、必要に応じてgsave・grestoreで囲む。 clipはカレントパスをクリアしないので、必要に応じてnewpathを実行する必要がある。 クリッピングパスは既存の、あるいはこれから描画するパスそのものをクリッピングするのではなく、実際にデバイスに対して線を描いたり、塗りつぶしたりするときに、つまり、strokeやfillなどのオペレータを実行するときに適用される。
あとこの場合はrectclipというオペレータがあるので、そっちを使った方が楽といえば楽。
基本的にはfillと同じルール(PLRM 4.5.2)。 ある点がクリッピングパスの内部に含まれるかそうでないかを調べる場合、とりあえずその点から無限遠に向かって線を引いてみる。 前者は、この線と交差したパスのうち左向きのものと右向きのものを別々に数えて、数が同じだったら外部、異なれば内部になる。 とても日本語に訳しにくいが、回転方向数によるクリッピングとでも言うのだろうか。
後者は交差したパスの数が偶数なら外部、奇数なら内部である。 これは偶奇ルールでいいだろう。
回転方向数を数える場合、左向きと右向きの数が同じになった場合に外部だが、このとき交差したパスの数は必ず偶数だ。 また、交差したパスの数が奇数の場合、左向きと右向きの数は異なるはずなので、必ず内側になる。 つまり、回転方向を数える場合も、偶奇ルールの場合も、外部なら交差したパスの数は偶数で、交差したパスの数が奇数なら必ず内部になる。 これらは対偶の関係にある。 回転方向を数える場合、逆は必ずしも真ではないので注意。 偶奇ルールなら逆も真である。 当たり前だが。
ここから、一番外側の線より外は必ず外部になることと、内部をくりぬきたいような場合、そこまでに必ず2本はパスがなければならないことが分かる。 つまり、穴を開けたい場合、その外側にもう一本パスが必要である。 穴を開けることが分かっている場合、あらかじめ境界矩形に相当する箱を定義しておくとよいのだが、座標系を変更すると矩形の大きさも変わってしまうので、グラフを書くで紹介したdefunitのような方法を使う必要があるだろう。
2個の図形を重ねると3個の領域ができる。 それぞれの領域に2進数の1ビットを割り当て、描画しない場合に0、描画領域に含める場合に1とすると、8通りの組み合わせがある。
このうち、000は描画しなければよい。011と110は単に図形をひとつ書いてclipである。
010はANDになるが、ANDの場合はfigure1 clip newpath figure2 clip
という書き方になる。
111はORになるが、ORの場合はfigure1 figure2 clip
になる。
clipすると必ず狭くなる、つまりANDになるので、ORにしたい場合は全部の領域を(同じ向きで)書いてからclipすることになる。
101は意外に簡単で、figure1 figure2 eoclip
である。あるいはfigure1とfigure2を逆向きに書いてclipしてもよい。
残った001と100が意外に面倒。
一方の図形を、もう一方の図形で削り取る形になる。
原理的にはfigure1 figure2 eoclip newpath figure1 clip
でよさそうに見えるが、実際にやってみると、
出てはいけないエッジが出てしまう。 このエッジを消すには、eoclipするときに残す方の図形を少し大きめに書いておく必要がある。 あるいは、削る側の図形をきちんと穴の形にしてからclipすればよい。
普通に穴の開いた図形で切り取る場合、先ほど書いたとおりさらに外側にもうひとつパスが必要である。 自分ですべての図形を囲む境界矩形を用意しておくか、pathbboxというオペレータを使って、
% x1 y1 x2 y2 rect - /rect { 3 index 3 index moveto 3 index 1 index lineto 1 index 1 index lineto 1 index 3 index lineto closepath pop pop pop pop } def figure1 clip figure2 pathbbox newpath rect figure2 eoclip
とする。 まじめに書くと
figure1 figure2 pathbbox newpath rect figure2 eoclip newpath figure1 clip
という感じになるが、clip系のオペレータは暗黙のnewpathを実行しない(パスが残る)のでそれをうまく利用したのが先に書いた方法になる。
pathbboxはパスを囲む矩形を返す便利なオペレータなのだが、若干注意がある。 ひとつは内部にcurvetoによるパスがある場合で、この場合はパスそのものではなく、制御点を使って境界矩形を計算する、という点である。 これをパスそのもので計算したい場合はflattenpathというオペレータを併用する。
もうひとつは計算そのものはデバイス空間で行われて、それをユーザー空間に変換した結果を返す、という点である(LPRM 8.2、pathbboxの項)。 パスを作るオペレータ類は点を指定した時点でのCTMに従うので、複数のCTMにまたがってパスを作った場合はこうするしかないのである(逆にstrokeはstrokeした時点のCTMで線の太さを計算する)。 もちろん、PostScriptインタプリタがユーザ空間に「逆変換」してくれるのだが、このときにデバイス空間の軸とユーザ空間の軸が平行になっていない、つまり、回転していたり斜めになっていたりすると、デバイス空間での境界矩形を含むユーザ空間での矩形・・・そのときのX軸・Y軸と各辺が平行になっている四角形・・・を返すようになるため、ユーザ空間だけで計算した場合よりも境界矩形が大きくなる。
この図形は単純に円を描いて境界矩形を計算させたもの。
点線はデフォルトユーザ空間で計算した境界矩形で、その外側の、斜めになった境界矩形は30 rotate
した後に計算した境界矩形である。
デフォルトユーザ空間で計算した境界矩形はパスをちょうど含む図形になっているが、座標軸をまわして(あるいは斜めにして)しまうと、元の境界矩形をちょうど含む境界矩形が生成されることが分かる。
Copyright (C) 2012-2016 akamoz.jp
$Id: clipping.htm,v 1.4 2016/02/20 08:56:10 you Exp $