SVGのフィルター効果
SVGには、要素にフィルター効果を与えるための特別な要素が用意されています。CSSのfilterプロパティよりも豊富なフィルターが再現できるから魅力的なんですよねー。けれど、パッと見ごちゃごちゃしてて分かり難いし、手順とか手続きがあって、なんだかとっつき難そう…。そんな「SVGのフィルター効果」について、個人的なまとめです:)。
SVGのフィルター効果のための要素
SVGはXMLに基づくマークアップ言語なので、フィルターを適用する方法も、XMLのタグ要素として用意されていて、フィルター効果のための要素は、下記ページの「Filter primitive elements」と「Light source elements」という項目にまとまっています。
すべてfe
という接頭辞(filter effectの略)が付いてるんですね:D!
SVGのフィルター効果は、これらフィルター要素を使って作ってゆくことになります。
フィルター要素の種類
SVGに用意されているフィルター要素は、ぜんぶで25個!
けど、中にはふたつでひとつみたいな要素もあるので、フィルターの種類的にはだいたい17種類くらい。
大きく、以下の4つのタイプに分けられるかなと思います:)。
- 重なりを制御するフィルター
<feMerge/>
(<feMergeNode/>
)
レイヤーを順に重ねる<feComposite/>
重なるレイヤーの合成方法を指定する<feBlend/>
重なるレイヤーの色の混ざり方を指定する<feDisplacementMap/>
明暗によって、重なっている部分の画素をずらす
- 画像を追加するフィルター
<feFlood/>
塗りつぶした矩形を配置する<feImage/>
画像を読み込み配置する<feTile/>
画像の任意の範囲をタイル化する
- 状態を操作するフィルター
<feColorMatrix/>
要素の色をRGBチャンネルごとに調整する<feComponentTransfer/>
(<feFuncR/>, <feFuncB/>, <feFuncG/>, <feFuncA/>
)
要素の明るさ、コントラスト、カラーバランス、しきい値を調整する<feConvolveMatrix/>
エンボス、エッジ検出、ぼかし、シャープネスなどを調整する<feOffset/>
要素の位置をずらす<feGaussianBlur/>
要素をぼかす<feDropShadow/>
要素の不透明部分に影を落とす<feMorphology/>
画素を侵食(暗い画素を広げる)・膨張(明るい画素を広げる)させる<feTurbulence/>
ノイズ、揺らぎによるごく自然な紋様を生成する(パーリンノイズによる描画)
- 照明効果フィルター
<feDiffuseLighting/>
(<fePointLight/>, <feDistantLight/>, <feSpotLight/>
)
照明効果をシミュレートする(不透明な箇所が盛り上がっているものとして扱う)<feSpecularLighting/>
(<fePointLight/>, <feDistantLight/>, <feSpotLight/>
)
鏡面反射効果をシミュレートする(透明な箇所が盛り上がっているものとして扱う)
「状態を操作する」のと「照明効果」が、実際にフィルター効果を適用する要素で、「重なりを制御する」のと「画像を追加する」のは、フィルター効果を補助する役割、という印象です。
これらのフィルター要素を使って、Photoshopのフィルターとかレイヤー効果とか色調補正みたいな効果を、ブラウザ上で再現することができます:)。
SVGのフィルターのやり方
まずはSVGでフィルターの作り方と使い方について、ここでは、シンプルなSVGフィルターをHTML要素に適用する方法を見てゆきます。
HTML内でSVGフィルターを使う手順は以下の通り:)。
- filter要素で、フィルター効果を定義して、名前を付ける
- その名前を、CSSのfilterプロパティで指定する
filter要素でフィルターを定義して、定義したフィルターを、CSSのfilterプロパティを使ってHTML要素に適用する、という流れになります。
詳しく見てゆきます!
フィルター効果を定義する
HTML内の適当な場所にsvg要素を用意します(ここでは</body>
の直前あたりに書いています)。svg要素の中にdefs要素を用意して、この中にフィルター効果を定義してゆきます。
SVGでは、symbol要素やfilter要素など、後で再利用するべく書いた定義する要素はdefs要素の中に書く事になってるんです。
今回は、中身がフィルターの定義だけなので、width属性とheight属性の値を0
に指定しておきます。
この中にfilter要素を書いて、前述のフィルター要素(fe
で始まる要素)を書いていくことで、自分だけのフィルター効果を定義します。フィルターの名前は、filter要素のid属性で指定します。
まずひとつ、カンタンンなフィルターを作ってみましょう。例えば、feGaussianBlur要素を使って以下のように書けば、「ぼかしフィルター」の出来上がり。ここでは分かりやすく、フィルター要素と同じfeGaussianBlur
という名前を付けました。
フィルターを適用する
フィルターを適用するには、CSSのfilterプロパティを使います。値にはurl()関数を使って、括弧の中に、さっき作ったぼかしフィルターの名前の頭に#
を付けて指定します。
フィルターを適用する要素にはfe-blur
というclass名を付けています。
フィルターが適用されて、パンちゃんがブレました! CSSでは一方向だけのぼかし(ブレ)はできないから助かりますねーX)。
けれど画像の下に、なにやら妙な余白がありますね(スクロールバーが出ちゃう)。これは、フィルターを定義しているsvg要素分の領域が影響してるみたいです。フィルターを定義しているだけなので隠しておきたいところ…:(。
表示されないように、display:none
しておくことにします!
余白がなくなりました。…けど、Chromeだとパンちゃんちょっと濃くなった…?
それにFirefoxではフィルターが外れちゃってます…。Firefoxでは、フィルターを定義しているsvg要素をdisplay:none
すると、フィルターの定義ごと消えちゃうみたいです…X((2023.4現在)。
display:none
じゃない方法でsvg要素だけ表示されないように、svg-filter
というclass名を付けて、以下のように念入りにスタイルを指定しておくことにします。
width/height
もスタイルで指定することにして、width/height属性は消しちゃいます。
妙な余白がなくなったまま、Firefoxでもパンちゃんがブレましたね;D!
defs要素、filter要素について詳しくは下記ページを参照のこと。
複数のフィルター要素を組み合わせる
フィルター要素をいろいろ組み合わせることで、もっと凄いエフェクトを作ることもできます。
とはいっても、複数のフィルター要素を組み合わせる場合は、フィルター要素ごとの手続きがあったりして、こんがらがりがち。ここでは、色収差を使った「色ずれエフェクト」の作り方を通して、慎重に見てゆきます:)!
「色収差」とは、レンズを通して像を映す際に、屈折率や光の分散が原因で生じる色ずれのこと。写真やイラストで、敢えてRGBをズラして重ねるテクニックをよく見かけますよねー:D。
Photoshopでは、「レイヤースタイル」→「レイヤー効果」→「高度な合成」のチャンネルという項目で、レイヤーごとにRGBを出し分ければ、簡単に「色ずれ」を再現することができます(下図)。
SVGでは、「要素の色をRGBチャンネルごとに調整する」フィルター要素と、「重ねたレイヤーの合成方法を指定する」フィルター要素を使って再現することができます(下サンプル)。
上サンプルでは、#chromaticAberration
という名前のフィルターを定義して、figure要素にそのフィルターを適用しています。
そんな「色ずれエフェクト」で使っているフィルター要素たちは以下の通り。
なにやら複雑ですね…。まずは一行ずつ、何をやっているのか順番に見てゆきます:D!
色の分解
画像の色をRGBチャンネル(三原色)に分解するには、feColorMatrix要素を使います。
feColorMatrix要素は「要素の色をRGBチャンネルごとに調整する」ことができるフィルター要素で、やってる事はけっこう難しいのに、割とカンタンに、画像のRGBを調整できてしまうスゴ要素です:D!
フィルター要素の1行めでまさに、feColorMatrix要素を使って、「Gチャンネル(緑)の色味」だけが出力されるよう、調整しています。
feColorMatrix要素では、values属性に指定した値に基づいて色を変化させるのですが、なにやら数字がいっぱい並んでいて難しそうですね…。
この数字の羅列は、変換行列といって、次元とか空間を数学的に表現するときに使われる、とっても難しい、数字や記号を矩形状に並べた、行と列のこと…:‹。
下図のように、5列×4行に並べ替えて見るとわかり良いです。RGBそれぞれに、5つの値でもって調整されるんですね:o。
各行の数値は左から、赤成分(r)、緑成分(g)、青成分(b)、透明度(a)と、全部の一括加算値(w)を表していて、下サンプルのように、Rr
(赤色の赤成分)、Gg
(緑色の緑成分)、Bb
(青色の青成分)、Aa
(透明度のアルファ成分)が1
の時が通常で、「元と同じ色」が適用された状態(変化なし)ということになります:o。
なんとなく数値をいじってみると、行列と色の関係性が把握できるはずです。
1行めでは、赤色と青色の行が全部0
になっているので、緑色の緑成分だけ、つまりRGBのGだけの色が適用されたってわけですねーX)。
2行めは、feOffset要素を使って、緑になった画像をズラしています(上のサンプルよりもちょっと多めにズラしてます)。
dx属性に-8px
と、dy属性に-8px
を指定することで、左上にズレることになります。
そしてresult属性※で、ここまでの状態をg
という名前で「保存する」ということをしています。※result属性について詳しくは後述します。
この段階で、適用した要素を見てみると、下サンプルのような状態になります。見事に緑だけですね:D!
違う色も作る
3〜6行めでは、同じように「赤だけ」と「青だけ」を作っています。よく見ると、values属性の値と、feOffset要素でのズラし方が違うだけで、ほとんど1〜2行めと同じことをしていますよね:D!
それぞれ、青だけにした時は右方向へズラして、赤だけにした時は下方向へズラしています。
feColorMatrix要素には、新たにin属性※が増えてて、SourceGraphic
という値を指定することで、フィルター要素を適用する対象を指定し直しています。それから、result属性※で、さっきはg
だったけど今度はb
という名前で「保存」しています。※in属性とresult属性について詳しくは後述します。
6行めまで書いた段階で、適用した要素を見てみると、下サンプルのような状態になります。赤いですね!
「あれ?緑と青は何処行った?」と思うかもしれないけれど、ひとまずじゃんじゃか次の行を見てゆきますね:)!
作った色を重ねる
残りの7〜8行めでは、feComposite要素を使って、緑だけの画像と青だけの画像を重ねています。
feComposite要素は、重なった部分の合成方法を指定できるフィルター要素で、ここでは、その方法にlighter
という値を使っています。
「あれ?緑と青は何処から持ってきた?」と思うかもしれないけれど、その秘密はin属性にあります。※in属性についても詳しくは後述します!
フィルターが適用される対象について
フィルター要素は「その時の見た目」に対して適用されます。
例えば、上のサンプルで1行めに書いたfeColorMatrix要素は、そのフィルターを適用した要素の「最初の状態」に対して適用され、2行めのfeOffset要素は、「緑になった状態」に対して適用されていました。
※つまり、filter要素内のグラフィックの状態は「常に上描きされてゆく」ということ。
ただし、このままフィルターを続けると、次、青くしたいのに、緑の成分だけにしちゃって青の成分がないから、青になれないです…。困っちゃいますね:(。
そこでin属性です:D。
in属性は、フィルター要素の適用元を明示的に指定します。ここでは、SourceGraphic
という値を指定していて、これは「最初の見た目」のことを指します。
つまり、「緑色にした時のことはいったん忘れて、改めて最初の状態からフィルターを作り直す」という事ができるんですねー:D。
一方、「緑にするフィルター」はというと、別途result属性を使って記憶しています。
result属性は、その値に指定した名前で、その時の状態を保持しておくことができるんです(※その時点での見た目をスクショして取っておくような感じ)。
上サンプルでは、緑にするフィルターを「g
」という名前、青にするフィルターを「b
」という名前で保持していました。そして、その保持しておいた「その時の見た目」は、in属性の値として使うことができます…(以下、枠外へ続く):D!
そんなこんなで、なんだかんだ手続きがまどろっこしいきらいがありますが…、改めて、「色ずれエフェクト」を作るためのフィルター要素を順番にまとめると、以下のようになります。
- 元の見た目を緑成分だけにして、左上にズラす。この時点では、画像が緑で表示されている。この状態を「
g
」という名前で保存 - 元の見た目(
SourceGraphic
)を青成分だけにして、右にズラす。この時点では、画像が青く表示されている。この状態を「b
」という名前で保存 - 元の見た目(
SourceGraphic
)を赤成分だけにして、下にズラす。この時点では、画像が赤く表示された状態
- 赤い見た目の上に、「緑の時の状態(
g
)」を重ねる。この時点では、赤と緑の画像が重なった状態 - 赤と緑が重なった見た目の上に、「青い時の状態(
b
)」を重ねる。赤と緑と青の画像が重なった状態(出来上がり)
feComposite要素のin属性にg
を適用すると、operator属性の適用元が「緑の時の状態」ってことになります。それが重なるのは、現時点の「赤い状態」。その赤い状態に、緑の時の状態が重なり、「lighter
(比較(明)モード)」で色が合成された、というわけです;)。(※「青い状態」も然り)
補足:つまり
feColorMatrix要素は「画像の色をRGBチャンネルごとに調整する」フィルターだったので、下に重なる画像は隠れちゃって、適用元の色が変わるだけでした。
けれど、feComposite要素は「重なるレイヤーの合成方法を指定する」フィルターなので、適用元の「緑の状態」が、その下の「赤い状態」に対して、「lighter
(比較(明))」で重なることで、下の画像は隠れずに、ふたつの画像がブレンドされたように表示されたってこと。
あとがき
HTML内にSVGでfilter要素を書いておかなければならない分、CSSフィルターよりも使い勝手は良くないけれど…、より多彩な表現ができたり、効果を自由に作り込むことができるのが、SVGフィルター良いところ;)。CSSフィルターにはできない効果もたくさんあるので、CSSフィルターとうまく併用していきたいですね!
できれば、フィルター効果を「filter.svg」みたいな個別のSVGファイルとして用意することができたら便利なんだけれど、対応してるのはFirefoxだけの様子なんですよね…:((2023.4現在)。
個人的には、Photoshopさながらの色調補正ができちゃう「feColorMatrix要素」とか、輪郭を抽出したりエンボス加工ができちゃう「feConvolveMatrix要素」とか、波模様に歪めたりノイズ加工できちゃう「feTurbulence要素」とか、いいなぁって思います:>。
以下のサイトで、SVGフィルターを使ったいろいろなアイデアや、デモが紹介されてます。JavaScriptと併用すれば、もっとユニークな演出が作れちゃいますねー。
以上、「SVGのフィルター効果について」でした;D!
参考文献。
便利さん。