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のフィルターとかレイヤー効果とか色調補正みたいな効果を、ブラウザ上で再現することができます:)

Photoshopのレイヤー効果、色調補正、フィルターの例

SVGのフィルターのやり方

まずはSVGでフィルターの作り方使い方について、ここでは、シンプルなSVGフィルターをHTML要素に適用する方法を見てゆきます。
HTML内でSVGフィルターを使う手順は以下の通り:)

  1. filter要素で、フィルター効果を定義して、名前を付ける
  2. その名前を、CSSのfilterプロパティで指定する

filter要素でフィルターを定義して、定義したフィルターを、CSSのfilterプロパティを使ってHTML要素に適用する、という流れになります。
詳しく見てゆきます!

フィルター効果を定義する

HTML内の適当な場所にsvg要素を用意します(ここでは</body>の直前あたりに書いています)。svg要素の中にdefs要素を用意して、この中にフィルター効果を定義してゆきます。
SVGでは、symbol要素filter要素など、後で再利用するべく書いた定義する要素defs要素の中に書く事になってるんです。
今回は、中身がフィルターの定義だけなので、width属性とheight属性の値を0に指定しておきます。

	︙
	<svg width="0" height="0"><defs>
		<!-- ここに定義を書く -->
	</defs></svg>
</body>

この中にfilter要素を書いて、前述のフィルター要素feで始まる要素)を書いていくことで、自分だけのフィルター効果を定義します。フィルターの名前は、filter要素のid属性で指定します。

filter要素の中にフィルター要素を使ってフィルター効果を作る

まずひとつ、カンタンンなフィルターを作ってみましょう。例えば、feGaussianBlur要素を使って以下のように書けば、「ぼかしフィルター」の出来上がり。ここでは分かりやすく、フィルター要素と同じfeGaussianBlurという名前を付けました。

<svg width="0" height="0"><defs>
	<filter id="feGaussianBlur">
		<feGaussianBlur stdDeviation="8 0"/>
	</filter>
</defs></svg>
ぼかしフィルターの定義

フィルターを適用する

フィルターを適用するには、CSSのfilterプロパティを使います。値にはurl()関数を使って、括弧の中に、さっき作ったぼかしフィルターの名前の頭に#を付けて指定します。
フィルターを適用する要素にはfe-blurというclass名を付けています。

.fe-blur {
	filter: url(#feGaussianBlur);
}
ぼかしフィルターの適用
<figure class="fe-blur">
	<img src="pan.svg" alt="">
</figure>

フィルターが適用されて、パンちゃんがブレました! CSSでは一方向だけのぼかし(ブレ)はできないから助かりますねーX)

けれど画像の下に、なにやら妙な余白がありますね(スクロールバーが出ちゃう)。これは、フィルターを定義しているsvg要素分の領域が影響してるみたいです。フィルターを定義しているだけなので隠しておきたいところ…:(
表示されないように、display:noneしておくことにします!

<svg width="0" height="0" style="display:none"><defs>
	<filter id="feGaussianBlur">
		︙

余白がなくなりました。…けど、Chromeだとパンちゃんちょっと濃くなった…?
それにFirefoxではフィルターが外れちゃってます…。Firefoxでは、フィルターを定義しているsvg要素をdisplay:noneすると、フィルターの定義ごと消えちゃうみたいです…X((2023.4現在)

Chrome/Safari/Firefoxでの表示

display:noneじゃない方法でsvg要素だけ表示されないように、svg-filterというclass名を付けて、以下のように念入りにスタイルを指定しておくことにします。
width/heightもスタイルで指定することにして、width/height属性は消しちゃいます。

<svg class="svg-filter"><defs>
	<filter id="feGaussianBlur">
		︙
.svg-filter {
	position: absolute;
	overflow: hidden;
	width: 0;
	height: 0;
	visibility: hidden;
}
入念に隠すためのスタイル

妙な余白がなくなったまま、Firefoxでもパンちゃんがブレましたね;D

defs要素、filter要素について詳しくは下記ページを参照のこと。

複数のフィルター要素を組み合わせる

フィルター要素をいろいろ組み合わせることで、もっと凄いエフェクトを作ることもできます。
とはいっても、複数のフィルター要素を組み合わせる場合は、フィルター要素ごとの手続きがあったりして、こんがらがりがち。ここでは、色収差いろしゅうさを使った「色ずれエフェクト」の作り方を通して、慎重に見てゆきます:)

「色収差」とは、レンズを通して像を映す際に、屈折率や光の分散が原因で生じる色ずれのこと。写真やイラストで、敢えてRGBをズラして重ねるテクニックをよく見かけますよねー:D

色収差を利用した「色ずれ」エフェクト

Photoshopでは、「レイヤースタイル」→「レイヤー効果」→「高度な合成」のチャンネルという項目で、レイヤーごとにRGBを出し分ければ、簡単に「色ずれ」を再現することができます(下図)

Photoshopのレイヤー効果を使った色ずれの再現

SVGでは、「要素の色をRGBチャンネルごとに調整する」フィルター要素と、「重ねたレイヤーの合成方法を指定する」フィルター要素を使って再現することができます(下サンプル)

SVGのフィルター要素を使った色ずれの再現

上サンプルでは、#chromaticAberrationという名前のフィルターを定義して、figure要素にそのフィルターを適用しています。

<figure class="fe-aberration">
	<img src="../img/sample.jpg" alt="">
</figure>
.fe-aberration {
	filter: url(#chromaticAberration);
}

そんな「色ずれエフェクト」で使っているフィルター要素たちは以下の通り。

<svg class="svg-filter"><defs>
	<filter id="chromaticAberration">
		<feColorMatrix type="matrix" values="0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0"/>
		<feOffset dx="-4" dy="-4" result="g"/>
		<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
		<feOffset dx="4" dy="0" result="b"/>
		<feColorMatrix in="SourceGraphic" type="matrix" values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"/>
		<feOffset dx="0" dy="4"/>
		<feComposite in="g" operator="lighter"/>
		<feComposite in="b" operator="lighter"/>
	</filter>
</defs></svg>

なにやら複雑ですね…。まずは一行ずつ、何をやっているのか順番に見てゆきます:D

色の分解

画像の色をRGBチャンネル(三原色)に分解するには、feColorMatrix要素を使います。
feColorMatrix要素は「要素の色をRGBチャンネルごとに調整する」ことができるフィルター要素で、やってる事はけっこう難しいのに、割とカンタンに、画像のRGBを調整できてしまうスゴ要素です:D

フィルター要素の1行めでまさに、feColorMatrix要素を使って、「Gチャンネル(緑)の色味」だけが出力されるよう、調整しています。

<feColorMatrix type="matrix" values="0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0"/>
Gチャンネル(緑)成分のみにするフィルター要素

feColorMatrix要素では、values属性に指定した値に基づいて色を変化させるのですが、なにやら数字がいっぱい並んでいて難しそうですね…。
この数字の羅列は、変換行列といって、次元とか空間を数学的に表現するときに使われる、とっても難しい、数字や記号を矩形状に並べた、行と列のこと…:‹
下図のように、5列×4行に並べ替えて見るとわかり良いです。RGBそれぞれに、5つの値でもって調整されるんですね:o

変換行列と色の関係

各行の数値は左から、赤成分(r)、緑成分(g)、青成分(b)、透明度(a)と、全部の一括加算値(w)を表していて、下サンプルのように、Rr(赤色の赤成分)Gg(緑色の緑成分)Bb(青色の青成分)Aa(透明度のアルファ成分)1の時が通常で、「元と同じ色」が適用された状態(変化なし)ということになります:o
なんとなく数値をいじってみると、行列と色の関係性が把握できるはずです。

<feColorMatrix type="matrix"
values="
	0 0 0 0 0 
	0 1 0 0 0 
	0 0 0 0 0 
	0 0 0 1 0
"/>

1行めでは、赤色と青色の行が全部0になっているので、緑色の緑成分だけ、つまりRGBのGだけの色が適用されたってわけですねーX)


2行めは、feOffset要素を使って、緑になった画像をズラしています(上のサンプルよりもちょっと多めにズラしてます)
dx属性-8pxと、dy属性-8pxを指定することで、左上にズレることになります。
そしてresult属性で、ここまでの状態gという名前で「保存する」ということをしています。※result属性について詳しくは後述します。

<feOffset dx="-8" dy="-8" result="g"/>
左上へズラすフィルター要素

この段階で、適用した要素を見てみると、下サンプルのような状態になります。見事に緑だけですね:D

違う色も作る

3〜6行めでは、同じように「赤だけ」と「青だけ」を作っています。よく見ると、values属性の値と、feOffset要素でのズラし方が違うだけで、ほとんど1〜2行めと同じことをしていますよね:D
それぞれ、青だけにした時は右方向へズラして、赤だけにした時は下方向へズラしています。

<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
<feOffset dx="4" dy="0" result="b"/>
<feColorMatrix in="SourceGraphic" type="matrix" values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"/>
<feOffset dx="0" dy="4"/>
「赤だけ」と「青だけ」を作るフィルター要素

feColorMatrix要素には、新たにin属性が増えてて、SourceGraphicという値を指定することで、フィルター要素を適用する対象を指定し直しています。それから、result属性で、さっきはgだったけど今度はbという名前で「保存」しています。※in属性とresult属性について詳しくは後述します。
6行めまで書いた段階で、適用した要素を見てみると、下サンプルのような状態になります。赤いですね!

「あれ?緑と青は何処行った?」と思うかもしれないけれど、ひとまずじゃんじゃか次の行を見てゆきますね:)

作った色を重ねる

残りの7〜8行めでは、feComposite要素を使って、緑だけの画像と青だけの画像を重ねています。
feComposite要素は、重なった部分の合成方法を指定できるフィルター要素で、ここでは、その方法にlighterという値を使っています。

<feComposite in="g" operator="lighter"/>
<feComposite in="b" operator="lighter"/>
「緑だけ」と「青だけ」を「比較(明)モード」重ねるフィルター要素

「あれ?緑と青は何処から持ってきた?」と思うかもしれないけれど、その秘密はin属性にあります。※in属性についても詳しくは後述します!

フィルターが適用される対象について

フィルター要素は「その時見た目グラフィック」に対して適用されます。
例えば、上のサンプルで1行めに書いたfeColorMatrix要素は、そのフィルターを適用した要素の「最初の状態」に対して適用され、2行めのfeOffset要素は、「緑になった状態」に対して適用されていました。
※つまり、filter要素内のグラフィックの状態は「常に上描きされてゆく」ということ。

1〜2行めでやっている事

ただし、このままフィルターを続けると、次、青くしたいのに、緑の成分だけにしちゃって青の成分がないから、青になれないです…。困っちゃいますね:(
そこでin属性です:D
in属性は、フィルター要素の適用元を明示的に指定します。ここでは、SourceGraphicという値を指定していて、これは「最初の見た目グラフィック」のことを指します。
つまり、「緑色にした時のことはいったん忘れて、改めて最初の状態からフィルターを作り直す」という事ができるんですねー:D

3〜4行めでやっている事

一方、「緑にするフィルター」はというと、別途result属性を使って記憶しています。
result属性は、その値に指定した名前で、その時の状態を保持しておくことができるんです(※その時点での見た目をスクショして取っておくような感じ)

上サンプルでは、緑にするフィルターを「g」という名前、青にするフィルターを「b」という名前で保持していました。そして、その保持しておいた「その時の見た目」は、in属性の値として使うことができます…(以下、枠外へ続く):D

そんなこんなで、なんだかんだ手続きがまどろっこしいきらいがありますが…、改めて、「色ずれエフェクト」を作るためのフィルター要素を順番にまとめると、以下のようになります。

  1. 元の見た目を緑成分だけにして、左上にズラす。この時点では、画像が緑で表示されている。この状態を「g」という名前で保存
  2. 元の見た目SourceGraphicを青成分だけにして、右にズラす。この時点では、画像が青く表示されている。この状態を「b」という名前で保存
  3. 元の見た目SourceGraphicを赤成分だけにして、下にズラす。この時点では、画像が赤く表示された状態
  1. 赤い見た目の上に、「緑の時の状態g」を重ねる。この時点では、赤と緑の画像が重なった状態
  2. 赤と緑が重なった見た目の上に、「青い時の状態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


参考文献。

便利さん。