« 2011年09月 | メイン | 2012年01月 »

2011年11月 アーカイブ

2011年11月11日

SVGで半透明図形の外側にドロップシャドーを付けるには?

暫くこのweblogを更新してないので、リハビリの為に何か書く。
これまで、代わりにTwitterとかをやってた訳ではなく、weblogとかを書きたく無くなった訳でもなく、ネタはあって細かいことは色々やってはいたのだが、何をやっても成果が出ないのである。考え事のネタだけが溜まっていく。物書き用のMacBookが不調で筆無精になってたり、部屋の地デジ化に時間を奪われたり、肺炎に罹ったりしたこともあったが、土日もクタクタで、そもそも頭を使うことに取り組む時間が激減してるので、トシなのだろう。

話を戻して、SVGでドロップシャドーを付けるには、SVG 1.1のドキュメント§15.1のサンプルコードのように、feGaussianBlurを使ってぼかしてずらしたものを先に描けば良いのだが、影を付ける対象が半透明だと、半透明の部分に自身の影が透けて暗くなってしまう。例えば、この方法で
dropshadow0.svg(注:この画像はJPEG、クリックするとSVGファイルが開きます)
この四角の上にある半透明の円に影を付けると、
dropshadow1.svg(注:リンク先はSVGのfilterに対応していないブラウザでは影が出ません)
このように、円の内側が暗くなってしまう。もし、円の外側にだけ影を付けたい場合、どうすれば良いだろうか。
手前に光源があれば、円の内側の半透明部分も影を作り出すんだから、円の内側に影が入り込むのは当たり前だろ、と言われればその通りである。この所、ブログの記事を書き始めてから問題設定に問題があることに気付いて、草稿をボツにすることが度々発生するのだが、今日はこのまま書き続ける。

上のSVGでは、SVG 1.1のドキュメントのサンプルコードに従って、

<filter id="dropShadow1" width="150%" height="150%">
<feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="5"/>
<feOffset in="blur" result="offsetBlur" dx="10" dy="10"/>
<feBlend in="SourceGraphic" in2="offsetBlur" mode="normal"/>
</filter>

このようなフィルターを定義しており、元の絵(SourceGraphic)と影との合成にfeBlendを使っているが、これをfeMergeに替えて
<filter id="dropShadow1" width="150%" height="150%">
<feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="5"/>
<feOffset in="blur" result="offsetBlur" dx="10" dy="10"/>
<feMerge>
<feMergeNode/> <!-- blurred and offset image -->
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>

にしても結果は同じである(feMergeはnormalモードでfeBlendするのと同じ)。

SVGでは、オブジェクトにα値がある限り、オブジェクトの重ね合わせにはα合成が発生するので、下の絵を消すかのようにα値ごと上書きすることはできない。<filter>〜<filter>の中では、feBlendやfeComposite等を使うことによりブレンドモードを変えることができ、特に<feComposite operator="arithmetic" k1="0" k2="0" k3="1" k4="0" />とすると重ねる絵で完全に上書きすることができるが、これらは矩形でしか行えないので、重ねる絵の何も無い部分(α=0のピクセル)も上書きして、下の絵を消してしまう。従って、

<filter id="shadow_normal" width="150%" height="150%">
<feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="5"/>
<feOffset in="blur" result="offsetBlur" dx="10" dy="10"/>
</filter>

これによってできる
dropshadow2.svg
こういう影の画像に元の絵を重ねるのではなくて、
dropshadow3.svg
こういう影の画像を作らないといけない。このdropshadow3.svgでは
<filter id="shadow_out" width="150%" height="150%">
<feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="5"/>
<feOffset in="blur" result="offsetBlur" dx="10" dy="10"/>
<feComposite in="SourceAlpha" in2="offsetBlur" operator="arithmetic"
k1="0" k2="-100" k3="1" k4="0" />
</filter>

のようにして、元の画像のα値を-100倍して重ねることにより、元の画像のα > 0.01の部分のαを0にしている。これで目的が達成できているように見えるが、元の画像を重ねると
dropshadow4.svg
このように、少し隙間ができてしまう。これは、元の画像の輪郭の、輪郭を外れた少し外側に、アンチエイリアシングによって生じる微小なαを持つピクセルがあるからだと考えられる。上の例では右下に影を付けているので、影を左上にずらせば隙間を隠せるのではないかとも思うが、
dropshadow5.svg
この円の右上のように、隙間は右下にできるとは限らない。

次に、フィルターではなくクリップやマスクを使って影をカットすることを考える。まず問題になるのは、<clipPath>によって作られるクリッピング領域はパスの内側を残すクリップだということである。SVGではクリップやマスクを反転させる術は無いように思える。パスの外側を塗り潰すことが難しいのと同様に、パスの外側を残すクリップを作るのも難しいのである。
しかし、clip-ruleにevenoddを指定することによって穴開き領域を作ることができることを利用して、十分に大きな領域の内側に消したい領域の穴を開けたクリップを作ることは可能である。
そう思って、

<clipPath id="does_not_work" clip-rule="evenodd">
<rect x="0" y="0" width="200" height="200"/> <!-- 十分大きな領域 -->
<circle cx="80" cy="80" r="50"/> <!-- 消したい部分 -->
</clipPath>

のようにしてクリッピング領域を作ってみたが、これはrect部分からcircle部分が抜き取られるのではなく、rect部分とcircle部分の和になってしまった。evenoddは1つのパスで完結しないと働かないようである。
そこで、上記のrectとcircleをpathに置き換えて連結してみた。
<clipPath id="shadow_clip" clip-rule="evenodd">
<path d="M0,0 H200 V200 H0 V0
M130,80
C130,107.6 107.6,130 80,130
C52.4,130 30,107.6 30,80
C30,52.4 52.4,30 80,30
C107.6,30 130,52.4 130,80
Z"
/>
</clipPath>

これによって影を切り取ると
dropshadow6.svg
このようになり、元の絵を重ねると、
dropshadow7.svg
となり、所望の外側だけのドロップシャドーが得られた。

とりあえず目的は達成できたが、問題点として、
(1) SVGファイル内に似たようなパスデータが何度も現れる
(2) 影をつける対象は必ず1つのパスで書けなければならない
がある。(1)は似たようなパスでクリップと影と本体を別々に書かないといけないことによる。影と本体は、次のdropshadow8.svgのようにxlinkとuseを使ってパスを共通化できるが、クリップとの共通化は難しい。(2)とも共通する課題だが、複数のパスデータをclip-rule="evenodd"が利くように繋げる手段や、複数の要素をclip-rule="evenodd"で並べる手段が見つからないのが原因である。
dropshadow8.svg(xlinkを使って少しまとめたもの)

やはり問題設定が適切でないということだろうか。

続きを読む "SVGで半透明図形の外側にドロップシャドーを付けるには?" »

About 2011年11月

2011年11月にブログ「Weblog on mebius.tokaichiba.jp」に投稿されたすべてのエントリーです。過去のものから新しいものへ順番に並んでいます。

前のアーカイブは2011年09月です。

次のアーカイブは2012年01月です。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。

Powered by
Movable Type 3.35