エンジニアの副業は週1からでも可能?副業の例や探し方も解説
- ITエンジニア
- 副業
画像にフィルタをかけて色彩や明るさを調整したい時、みなさんはどのような方法を使っていますか?
CSSのfilterプロパティを使うことで画像の彩度・明るさ・コントラストなどが調整できます。CSSには11種類のフィルタがあり、ぼかし・コントラスト・彩度調整などの機能があります。(CSSのフィルタ機能について詳しく知りたい方はこちらの記事をどうぞ)
便利なCSSフィルタですが、機能が限られているという弱点もあります。CSSが有効なのは画像のみで、色調調整や簡単なぼかしといった加工しかできません。画像だけではなく、色々な種類の要素に対し加工をしたい場合、より充実した機能が必要となります。
そのような時に役立つのがSVGフィルタです。実はSVGを使った機能は10年以上前から存在していたのですが、CSSほど知られた存在ではありませんでした。この記事では「プリミティブな機能」として知られるSVGフィルタの機能を解説していきます。
まず初めに、CSSフィルタがSVGから派生していることはご存知でしたか?CSSフィルタはSVGが持つフィルタ効果の一部を最適化したもので、以前からSVG仕様内にあります。
CSSに比べ、SVGにはより多くのフィルタ効果があり、SVGフィルタの方が遥かに強力で複雑な効果を生み出せます。例えば、要素にぼかし効果を与えるCSSのblur()フィルタ機能。この機能を使うことで、要素に均一な「ガウスぼかし」がかかります。右下の画像はCSSで6pxのぼかしフィルタをかけたものです。
blur()を使ってX・Y軸の両方に均一なぼかしをかけることもできます。ただし、これはSVGのぼかしフィルタを簡易化したもので、その機能は限られています。SVGフィルタを使えば、均一なぼかしも、X・Y軸どちらか一方向のぼかしも両方が可能です。
▲SVGフィルタによるぼかし効果 左:X軸 右:Y軸
さらに、SVGフィルタはHTML要素とSVG要素への適用が可能です。CSSでurl()フィルタ機能をすることで、SVGフィルタ効果をHTML要素に適用できます。例えば、SVGで「myAwesomeEffect」というID名のフィルタ効果を作成する場合、下記のようにHTML要素や画像にフィルタががけられます(SVGでのフィルタ効果の定義については後ほど説明します)。
.el {
filter: url(#myAwesomeEffect);
}
SVGフィルタは非常に優秀で、数行のコードを書くだけでブラウザ上でPhotoshop級の効果を演出できます。SVGフィルタのポテンシャルを知り、ぜひご自分のプロジェクトでも使ってみてください。
ブラウザがSVGフィルタをサポートしているか気になる方もいるでしょう。
ほとんどのSVGフィルタは広くサポートされています。しかし、効果の適用具合はブラウザ間で多少違いが出ることがあります。これはSVGフィルタ効果に使われる個々のフィルタプリミティブに対するサポート状況や、ブラウザバグによるものです。また、SVGフィルタを適用するのがSVG要素かHTML要素かによってもブラウザサポート状況が変わることがあります。
そこで、フィルタ効果をUI向上の手法として使うことをおすすめします。フィルタ効果を使うのはフィルタなしでも問題なく使える要素だけにして、あくまで要素の見た目をより良くする目的で使用しましょう。そうすれば、ブラウザサポートに対してそこまで神経質になる必要はありません。
では、SVGでどのようにフィルタを定義・作成するかを見ていきましょう。
(※SVGフィルタは全体的に広くサポートされているとはいえ、この後紹介する効果のうち一部はまだ実験段階です。ご了承ください)
SVGの線状グラデーション・マスク・パターンや他のグラフィック効果と同じように、フィルタには専用の<filter>要素があります。
<filter>要素は直接レンダリングされず、SVGではfilter属性と一緒に、またはCSSではurl()機能と一緒に使用されます。このような要素(明示的に参照されない限りレンダリングされない要素)は通常、SVGの<defs>要素内のテンプレートとして定義されます。しかし、SVGの<filter>はdefs要素で囲む必要はありません。defs要素で囲んでも囲まなくても表示されないからです。
これはフィルタ効果を適用するソース画像が必要なためです。フィルタをソース画像へ呼び出すことで画像を明示的に定義しない限り、レンダリングする対象がないので反映されません。
下記はSVGフィルタを定義し、ソース画像に適用するためのごく基本的で、シンプルなサンプルコードです。
<svg width="600" height="450" viewBox="0 0 600 450">
<filter id="myFilter">
<!-- filter effects go in here -->
</filter>
<image xlink:href="..."
width="100%" height="100%" x="0" y="0"
filter="url(#myFilter)"></image>
</svg>
このサンプルコードではこの時点でフィルタは反映されません。なぜならまだ空っぽだからです。フィルタ効果を作成するには、filter要素内で効果を発揮するフィルタ操作を1つ以上定義する必要があります。つまり、filter要素はフィルタ効果を作り出すフィルタ操作を組み合わせたものの入れ物だということです。このフィルタ操作はSVGにおいて「フィルタプリミティブ」と呼ばれます。
SVGにおいて、各<filter>要素は一式のフィルタプリミティブを子要素として持っています。各フィルタプリミティブが1つ以上のインプットに対し基本的な画像操作を実行し、グラフィカルな結果を演出します。
フィルタプリミティブは画像操作の内容によって名前がつけられています。例えば、ソース画像にガウスぼかし効果を適用するプリミティブはfeGaussianBlurといいます。すべてのプリミティブにはfe(「filter effect」の略)というプレフィックスがつきます。(前述の通り、SVGにおける要素の名前はその機能にちなんでいます。)
以下のスニペットは、画像に5pxのガウスぼかしを適用する場合のフィルタのコードです。
<svg width="600" height="450" viewBox="0 0 600 450">
<filter id="myFilter">
<feGaussianBlur stDeviation="5"></feGaussianBlur>
</filter>
<image xlink:href="..."
width="100%" height="100%" x="0" y="0"
filter="url(#myFilter)"></image>
</svg>
SVGフィルタの仕様には現在17種類のフィルタプリミティブがあります。これらを使うことで、ノイズ・テクスチャ作成、ライティング効果、チャンネルごとの色調補正やその他多くの強力なグラフィック効果を演出できます。
フィルタプリミティブは、ソース画像をインプットし、別物としてアウトプットする仕組みです。1つのフィルタ効果のアウトプットもまた別物を作るためのインプットとして使用できます。つまりフィルタの組み合わせはほぼ無限にあり、作り出せるグラフィック効果の数は数え切れないほどあるのです。
各フィルタプリミティブは1つまたは2つの画像をインプットできますが、アウトプットの結果は1つだけです。フィルタプリミティブのインプットはin属性で定義され、操作のアウトプットはresult属性で定義されます。2つ目のインプットがある場合はin2属性となります。操作の結果は、他の操作に対するインプットとしても使えますが、操作のインプットがin属性で明示されていない場合、前回の操作結果が自動的にインプットとして使われます。プリミティブの結果を明示しない場合、その結果は自動的に次のプリミティブのインプットとして使われます。これについてはサンプルコードを見るとわかりやすいでしょう。
他のプリミティブの結果をインプットとして使うのに加え、フィルタプリミティブは他の種類のインプットも使えます。以下はその中でも重要な2つです。
SVGフィルタを使ううちに、ソース画像をインプットとして使いたい場合や、そのアルファチャンネルのみを使いたい場合が出てきます。この記事では、どの場面で何を使うべきかの例を紹介していきます。
以下のスニペットは、子要素としてのフィルタプリミティブがたくさんある場合のサンプルコードです。ここでは、プリミティブの働きは特に気にせず、特定のプリミティブのインプットとアウトプットがどのように定義され使用されているかに注目してください。わかりやすくするために一部コメントを入れています。
<svg width="600" height="400" viewBox="0 0 850 650">
<filter id="filter">
<feOffset in="SourceAlpha" dx="20" dy="20"></feOffset>
<!-- 前のフィルタは結果が定義されていなかったため、それに続くこのフィルタにはインプットがセットされていません。上記のプリミティブの結果が次のフィルタの結果として自動的に使われます。 -->
<feGaussianBlur stdDeviation="10" result="DROP"></feGaussianBlur>
<!-- 結果名は常に全角で設定・定義しましょう。目立ちやすくなり、コード全体の可読性が向上します。 -->
<feFlood flood-color="#000" result="COLOR"></feFlood>
<!-- このプリミティブは上記のプリミティブ2つをインプットとして使い、新たな効果をアウトプットしています。 -->
<feComposite in="DROP" in2="COLOR" operator="in" result="SHADOW1"></feComposite>
<feComponentTransfer in="SHADOW1" result="SHADOW">
<feFuncA type="table" tableValues="0 0.5"></feFuncA>
</feComponentTransfer>
<!-- DOMの順番に関わらず、どのような2つの結果でもあらゆるプリミティブのインプットとして使えます。 -->
<feMerge>
<feMergeNode in="SHADOW"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<image xlink:href="..." x="0" y="0" width="100%" height="100%" filter="url(#filter)"></image>
</svg>
フィルタの例をお見せする前に、簡単に説明したいのが「フィルタ領域」というコンセプトです。
フィルタ操作一式にはその効果を適用する領域が必要です。例えば、たくさんの要素を含む複雑なSVGを使っている場合や、特定の領域またはそのSVG内の一部の要素にだけフィルタ効果を適用したい場合などです。
SVGにおいて要素は「領域」を持っており、その境界は要素のバウンディングボックスの境界線により定義されています。バウンディングボックス(「bbox」と略されることも)とは、要素を取り囲む最小の長方形のことです。例えば以下のテキストの場合、ピンクの枠の長方形がバウンディングボックスとなります。
▲テキストを取り囲む最小の長方形
バウンディングボックスの高さを計算する時にテキストの行の高さが考慮され、この長方形の縦方向にもっと余白が入ることもあります。
要素が持つ既定のフィルタ領域は要素のバウンディングボックスです。よってテキストにフィルタ効果を適用する場合、効果が適用されるのはこの長方形の領域のみとなり、この境界からはみ出すフィルタの結果は切り取られます。道理にかなってはいますが厄介な場合もあります。というのも、多くのフィルタはバウンディングボックスの境界外に若干はみ出すピクセルにまで作用するのですが、その部分が切り取られてしまうからです。
以下の例を見てみましょう。テキストにぼかし効果を適用しましたが、 左右の端でぼかし効果が切り取られています。
▲バウンディングボックスの境界の左右端において、ぼかし効果が切り取られている
この問題を防ぐ方法はあるのでしょうか? 答えは簡単、フィルタ領域を拡大すればよいのです。<filter>要素のx、y、幅、高さ属性を調整することで、フィルタが適用される領域を拡大できます。
SVGの仕様では以下のように規定されています。
フィルタ効果が対象オブジェクトのバウンディングボックスの外にわずかにはみ出す部分にも作用することがあるので、往々にしてフィルタ領域にパディングスペースを追加することが必要です。このため、「x」と「y」にマイナスのパーセンテージ値を、「幅」と「高さ」に100%以上のパーセンテージを設定できます。
デフォルトで、フィルタは四方向にバウンディングボックスの幅・高さの10%以上の領域を持っています。つまりx、y、幅、高さ属性の既定値は以下のようになります。
<filter x="-10%" y="-10%" width="120%" height="120%"
filterUnits="objectBoundingBox">
<!-- フィルタ操作がここに入る -->
</filter>
<filter>要素でこれらの属性を設定しない場合、上記の値がデフォルトで設定されます。必要に合わせて値を変え、領域を拡大または縮小できます。
ここで注意したいのは、x、y、幅、高さ属性に使われる単位はfilterUnitsの値に依存するという点です。filterUnits属性はx、y、幅、高さ属性の座標システムを定義し、以下2つのうち1つの値をとります。
<!-- objectBoundingBoxの単位を使う場合 -->
<filter id="filter"
x="5%" y="5%" width="100%" height="100%">
<!-- userSpaceOnUseの単位を使う場合 -->
<filter id="filter"
filterUnits="userSpaceOnUse"
x="5px" y="5px" width="500px" height="350px">
フィルタ領域の広がりを視覚的に確認したい場合、フィルタ領域を色で塗りつぶすことで可視化できます。便利なことに、feFloodというフィルタプリミティブを使うことで現在のフィルタ領域にflood-color属性で指定した色が付けられます。
以下はテキストのフィルタ領域を可視化する場合のサンプルコードです。
<svg width="600px" height="400px" viewBox="0 0 600 400">
<filter id="flooder" x="0" y="0" width="100%" height="100%">
<feFlood flood-color="#EB0066" flood-opacity=".9"></feFlood>
</filter>
<text dx="100" dy="200" font-size="150" font-weight="bold" filter="url(#flooder)">Effect!</text>
</svg>
このコードからわかるように、feFloodプリミティブにはflood-opacity属性も使えるため、色の透明度が調整できます。
上記のコードではフィルタ領域がピンク色に塗りつぶされます。ただし、文字通り領域を色で塗り「つぶす」ので、テキストだけでなくフィルタ領域に作成した要素や効果のすべてが覆われてしまいます。
▲フィルタ領域を塗りつぶす前と後
これを避けるには、色レイヤーを「後ろ」に移動してテキストレイヤーを最正面に持ってくる必要があります。
SVGフィルタで重ね合わせて表示させたい要素に複数のレイヤーをかける場合、<feMerge>というフィルタプリミティブが使えます。その名の通り、feMergeプリミティブは要素や効果のレイヤーを統合してくれます。
<feMerge>プリミティブにはin属性がありません。レイヤーを統合するにはfeMerge内で2つ以上の<feMergeNode>が使われ、追加したいレイヤーを示すin属性をそれぞれが持っています。
レイヤー(または「ノード」)の積み重ねは<feMergeNode>のソースの順番に依存します。1つ目の<feMergeNode>は2つ目の「後ろ」または「下」にレンダリングされ、最後の<feMergeNode>が最前面のレイヤーになるといった具合です。
先程のテキストを使った例では、塗りつぶした色がレイヤー1で、ソーステキスト(ソース画像)がレイヤー2です。塗りつぶした色の上にテキストを持って来たい場合、コードは以下のようになります。
<svg width="600px" height="400px" viewBox="0 0 600 400">
<filter id="flooder">
<feFlood flood-color="#EB0066" flood-opacity=".9" result="FLOOD"></feFlood>
<feMerge>
<feMergeNode in="FLOOD" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<text dx="100" dy="200" font-size="150" font-weight="bold" filter="url(#flooder)">Effect!</text>
</svg>
feFloodにおいてresult属性に名前をつけることで、<feMergeNode>でそれをインプットとして呼び出しています。塗りつぶした色の上にテキストを表示したいので、SourceGraphicを使ってテキストを参照します。以下はこのコードのライブデモです。
SVGフィルタの世界が少し見えてきましたか?では次に、簡単なドロップシャドウをSVGで作成してみましょう。
最初に断っておきますが、簡単なドロップシャドウであれば、CSSのdrop-shadow()フィルタ機能を使った方が速いです。SVGフィルタを使うとより長いコードを書かなければなりません。前述した通り、CSSフィルタ機能はSVGの便利なショートカットなのです。しかし、今後の記事で紹介していく複雑なフィルタ効果への導入として、ここではSVGを使ってみましょう。
まず、ドロップシャドウはどのように作られるのでしょうか?
ドロップシャドウとは通常、要素の後ろまたは下に表示される、要素と同じ形状をした薄い灰色のレイヤーのことを言います。その要素のコピーがぶれて灰色になったものを思い浮かべてもいいでしょう。
SVGフィルタを作成するにあたり、特定の効果を演出するにはどのようなステップが必要なのか考える必要があります。ドロップシャドウの場合、次の3ステップです。
はじめの黒色のコピーは、要素のアルファチャンネルや、SourceAlphaをフィルタのインプットとして使うことで作れます。
feGaussianBlurプリミティブはSourceAlphaレイヤーにガウスぼかしを適用するのに使われます。ぼかしのレベルはstdDeviation(Standard Deviationの略)属性で指定できます。stdDeviation属性に1つの値を設定する場合、インプットに対し均一なぼかし効果が与えられます。2つの数値を設定するのも可能です。1つ目の数値は水平方向、2つ目の数値は垂直方向のぼかしに使われます。ドロップシャドウの場合は均一なぼけを与えたいので、コードの出だしは以下のようになります。
<svg width="600" height="400" viewBox="0 0 850 650">
<filter id="drop-shadow">
<!-- ソース画像の黒色のコピーを取得し、10のぼけを与える -->
<feGaussianBlur in="SourceAlpha" stdDeviation="10" result="DROP"></feGaussianBlur>
</filter>
<image xlink:href="..." x="0" y="0" width="100%" height="100%" filter="url(#drop-shadow)"></image>
</svg>
上記のコードを実行すると以下のような結果になります。この時点では、画像のぼかしがかけられたアルファチャンネルのみがレンダリングされています。
次に、ドロップシャドウの色を灰色にしてみましょう。フィルタ領域を塗りつぶし、その色レイヤーを先程作成したドロップシャドウレイヤーと合成します。
合成とは、グラフィック要素とそのバックドロップを組み合わせることです。バックドロップは要素の後ろにあるコンテンツで、要素を合成するものを指します。このフィルタでは塗りつぶした色が上のレイヤーで、ぼかしシャドウがそのバックドロップです(後ろにあるため)。feCompositeプリミティブは今後の記事で頻繁に登場するので、合成のしくみがよく分からない方は、合成の基本をわかりやすくまとめたこちらの記事を読んでみてください。
feCompositeプリミティブには、どの合成操作を使うかを指定するoperator属性があります。
合成operatorのinを使うことで、塗りつぶした色のレイヤーが「切り取られ」、シャドウレイヤーと重なり合う色の部分だけがレンダリングされます。2つのレイヤーはお互いが交差するところで混ざり合い、黒色のドロップシャドウが灰色で色付けされます。
feCompositeプリミティブにはin、in2属性で指定された2つのインプットが必要です。この例では、1つ目のインプットは色のレイヤーで、2つ目はぶれを加えたシャドウバックドロップです。operator属性で指定した合成操作を加えると、コードは以下のようになります。
<svg width="600" height="400" viewBox="0 0 850 650">
<filter id="drop-shadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="10" result="DROP"></feGaussianBlur>
<feFlood flood-color="#bbb" result="COLOR"></feFlood>
<feComposite in="COLOR" in2="DROP" operator="in" result="SHADOW"></feComposite>
</filter>
<image xlink:href="..." x="0" y="0" width="100%" height="100%" filter="url(#drop-shadow)"></image>
</svg>
feGaussianBlur、feFloodプリミティブのresultがfeCompositeのインプットとして使われているのに注目してください。デモは以下のようになります。
元の画像をドロップシャドウの上に重ねる前に、ドロップシャドウに垂直・水平方向のオフセットをつけましょう。シャドウをどの方向にどれだけずらすかはお好みで。この例では、光源がスクリーンの左上コーナーにあると仮定し、シャドウを右下に少しだけずらします。
SVGでレイヤーにオフセットをつけるにはfeOffsetプリミティブを使います。in、result属性に加え、このプリミティブはdx、dyという2つの主要属性をとります。これらを使ってx、y軸を調整し、レイヤーをずらす距離を設定します。
ドロップシャドウにオフセットをつけたら、feMergeを使ってソース画像と統合しましょう。前のセクションでテキストと塗りつぶした色を統合したのと似ています。1つ目のmergeNodeがドロップシャドウをインプットとしてとり、2つ目はSourceGraphicをインプットとしてソース画像を重ね合わせます。最終的なコードは以下のようになります。
<svg width="600" height="400" viewBox="0 0 850 650">
<filter id="drop-shadow">
<!-- ソースアルファを取得しぼかしを与える。結果には「DROP」と名前をつける -->
<feGaussianBlur in="SourceAlpha" stdDeviation="10" result="DROP"></feGaussianBlur>
<!-- 薄い灰色で領域を塗りつぶす。このレイヤーには「COLOR」と名前をつける -->
<feFlood flood-color="#bbb" result="COLOR"></feFlood>
<!-- DROPとCOLORレイヤーを合成し、シャドウに色を付ける。結果には「SHADOW」と名前をつける -->
<feComposite in="COLOR" in2="DROP" operator="in" result="SHADOW"></feComposite>
<!-- SHADOWレイヤーを右下に20ピクセル分ずらす。新しいレイヤーには「DROPSHADOW」と名前をつける -->
<feOffset in="SHADOW" dx="20" dy="20" result="DROPSHADOW"></feOffset>
<!-- 画像が上にくるように、DROPSHADOWとソース画像を重ね合わせる。(MergeNodeの順番が反映されることを忘れずに) -->
<feMerge>
<feMergeNode in="DROPSHADOW"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<!-- 「filter」属性でフィルタをソース画像に適用する -->
<image xlink:href="..." x="0" y="0" width="100%" height="100%" filter="url(#drop-shadow)"></image>
</svg>
以下は上記コードのライブデモです。
以上がSVGでフィルタ効果を作成する方法です。この効果はすべての主要ブラウザで使えます。
上で紹介したfeFloodを使ってドロップシャドウに色付けする方法は、今後頻繁に必要になる色付けテクニックです。黒や灰色以外のカラフルなシャドウを作成したい時にも使えるので、覚えておくと非常に便利です。
以下では、より一般的に使われているドロップシャドウの作成方法を紹介します。黒色のシャドウに透明度を与えることで、結果的に色を薄くする方法です。
レイヤーの不透明度を調整するには、feColorMatrixまたはfeComponentTransferプリミティブを使います。feComponentTransferプリミティブについては今後の記事でより詳しく説明するので、今回はfeColorMatrixを使ってシャドウの不透明度を下げてみましょう。
feColorMatrixプリミティブだけで1件の記事が書けてしまうくらい内容が盛りだくさんなので、一度ウナ・クラベットの記事を読むことをおすすめします。初心者にもわかりやすい例を使って解説されています。
端的に言うと、このフィルタはインプット画像のピクセルごとのR(赤)・G(緑)・B(青)・A(アルファ)チャンネルに行列変換を適用することで、新たな色とアルファ値を持つ結果を作り出しています。つまり、行列操作によってオブジェクトの色を補正しているのです。基本的なカラーマトリクスは以下のようになります。
<filter id="myFilter">
<feColorMatrix
type="matrix"
values="R 0 0 0 0
0 G 0 0 0
0 0 B 0 0
0 0 0 A 0 "/>
</feColorMatrix>
</filter>
ここではシャドウの不透明度を下げたいだけなので、RGBチャンネルを変えないアイデンティティマトリクスを使います。しかし、マトリクスのアルファチャンネルの値は下げる必要があります。
<filter id="filter">
<!-- ソースアルファを取得し、ぼかしを加える -->
<feGaussianBlur in="SourceAlpha" stdDeviation="10" result="DROP"></feGaussianBlur>
<!-- ドロップシャドウにオフセットをつける -->
<feOffset in="SHADOW" dx="20" dy="20" result="DROPSHADOW"></feOffset>
<!-- アルファチャンネルの値を0.3に下げ、シャドウの透明度を上げる -->
<feColorMatrix type="matrix" in="DROPSHADOW" result="FINALSHADOW"
values="1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 0.3 0">
</feColorMatrix>
<!-- シャドウとソース画像を統合する -->
<feMerge>
<feMergeNode in="FINALHADOW"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
以下は上記のコードのライブデモです。
このシリーズでは、フィルタ操作の技術的な定義はなるべく避けて、よりシンプルでわかりやすく紹介していきます。何かを学ぶ時というのは、必ずしもすべてを細かく理解する必要はありません。私は、フィルタの機能を使いこなすには、「フィルタで何ができるか」「どのように使うか」を理解すれば十分だと思います。
より詳しく学びたい方は、まずは仕様に目を通してみましょう。とはいえ、あなたの疑問への答えがすべて仕様に書かれている訳ではありません。自分で調べる必要も出てくるでしょう。このシリーズの最終回の記事で非常に役立つリソースのリストを紹介するので、今後の参考にしてください。
今回はSVGフィルタの基本を学び、実際にフィルタを作成・適用しました。次回からはさらなるフィルタプリミティブを使った効果の例を見ていきます。お楽しみに!
(原文:Sara Soueidan 翻訳:Yui Tamura 編集:Workship MAGAZINE編集部)