連載
» 2012年07月02日 00時00分 公開

Webグラフィックをハックする(3):SVGで図形やアニメを描画してみよう (4/5)

[伊藤千光,WebOS Goodies]

SVGフィルタの基礎

 SVGのようなベクトルグラフィックスは図形を組み合わせて全体を構成していくため、どうしても輪郭のはっきりした画像になります。グラフや略図のようなものであれば問題ありませんが、凝ったイラストを描くにはどうしても限界があるでしょう。

 こうした欠点を補うのが「SVGフィルタ」です。その名前のとおり、Photoshopなどのグラフィックツールによくあるぼかしや色変換などのフィルタをSVGの描画結果に適用する機能です。

 SVGフィルタの使い方は、前回取り上げたグラデーションとよく似ています。filterタグを使ってあらかじめフィルタリングの内容を定義しておき、適用先の図形のfilter属性でそれを参照します。以下は単純なぼかし(ガウシアンブラー)フィルタを適用する例です。

シンプルなフィルタ シンプルなフィルタ 実際のサンプルを見る

 このように、SVGフィルタではfilterタグ内に各フィルタに対応するタグを記述することで、適用する効果を選択します。ここで使っているfeGaussianBlurタグは、ガウシアンブラーを表現するタグです。stdDeviation属性はブラーの半径になります。

 とても直感的ですが、1つ落とし穴があります。このままブラーの半径(stdDeviation属性の値)を大きくしていくと、下の例のように境界線のようなものが見えてしまいます。

フィルタの効果範囲 フィルタの効果範囲 実際のサンプルを見る

 これは、フィルタの効果範囲が限られていることによるものです。デフォルトでは、適用対象のバウンディングボックスの上下左右に10%のマージンを取った領域が効果範囲となり、その領域より外側にはフィルタの結果が描画されません。効果範囲を変更するには、filterタグのx、y、width、heightにそれぞれ効果範囲の左上座標と幅、高さを指定します。上の例も、それらの属性で効果範囲を上下左右20%に調整すると、正しく表示されます。

効果範囲の調整 効果範囲の調整 実際のサンプルを見る

 もう1つの注意点として、各フィルタのパラメータ(stdDeviation属性など)がユーザー座標系(適用先の図形が乗っている座標系)で定義されることです。通常はこの方が値の調整などがしやすいはずですが、図形のサイズを変更しても効果が調整されないという欠点があります。

 フィルタのパラメータを適用先のオブジェクトのサイズに連動させるためには、filterタグのprimitiveUnits属性に「objectBoundingBox」を指定します。こうすると、各パラメータは図形の幅・高さを1とする座標系で解釈され、図形のサイズに効果が追従するようになります。

オブジェクト座標系による効果の指定 オブジェクト座標系による効果の指定 実際のサンプルを見る

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

 ガウシアンブラーを実現するfeGaussianBlurタグはすでに説明済みですが、同じようにフィルタリング方法を定義するためタグとして、以下のものが用意されています。

  • feBlend …… 2つのフィルタリング結果をブレンドする
  • feColorMatrix …… 行列で色を変換する
  • feComponentTransfer …… RGBAの各要素ごとに色を変換する
  • feComposite …… 2つのフィルタリング結果を合成する
  • feConvolveMatrix …… 畳み込み演算
  • feDiffuseLighting …… 拡散光によるライティング
  • feDisplacementMap …… ピクセル単位でサンプリング位置を変化させる
  • feFlood …… 単色による塗り潰し
  • feGaussianBlur …… ガウシアンブラー
  • feImage …… 画像(PNG, JPEG, SVG)を描画
  • feMerge …… 複数のフィルタリング結果を重ねる
  • feMorphology …… 主に輪郭を外に広げたり、内側に狭めたりする
  • feOffset …… 画像を並行移動する
  • feSpecularLighting …… 鏡面反射光によるライティング
  • feTile …… ソース画像をタイル状に繰り返して並べる
  • feTurbulence …… Perlinノイズを生成する

 このリストを見てどう感じるでしょうか。「ああ、なるほどね」と納得されるのは、おそらく画像処理に関する知識のある方だと思います。多くの読者は、普段馴染みのあるフィルタが見当たらないことに戸惑ってしまうでしょう。

 SVGでプリセットされているフィルタは、実はグラフィックツールなどに実装されているフィルタを構成するパーツのようなもので、「フィルタプリミティブ」と呼ばれています。複数のフィルタプリミティブを組み合わせることで、多彩な効果が実現できます。

 それでは、複数のフィルタプリミティブを組み合わせる方法を見ていきましょう。まず、あるフィルタの出力に別のフィルタをかける、つまりフィルタプリミティブを直列につなぐには、単にfilterタグ内に複数のフィルタプリミティブを並べます。以下は、feColorMatrixによる色変換(色相変換)の後にガウシアンブラーをかける例です。

フィルタリング結果に別のフィルタをかける フィルタリング結果に別のフィルタをかける 実際のサンプルを見る

 フィルタプリミティブを並列にかけてその結果を合成するには、いくつかの方法があります。まず、複数のフィルタの結果を単純に重ね合わせるには、feMergeタグを使います。以下は、feColorMatrixによる色相変換の結果と、アルファチャンネルの平行移動(feOffset)による影を重ねる例です。

フィルタリング結果を重ねる フィルタリング結果を重ねる 実際のサンプルを見る

※ 上記のコード例はfilterタグの内部のみを示しています。以下同様です。

 feColorMatrixタグとfeOffsetタグには、result属性が記述されています。これは出力結果に名前を付けて、後続のフィルタプリミティブで参照できるようにするものです。

 さらにfeOffsetタグ(およびfeMergeNodeタグ)には、in属性も記述されています。これはフィルタの入力元を指定する属性で、result属性で定義した名前か、もしくは以下のいずれかになります。

  • SourceGraphic …… フィルタを適用する図形の画像
  • SourceAlpha …… アルファチャンネルのみのSourceGraphic。カラーチャンネルはすべて黒になる
  • BackgroundImage …… フィルタの効果範囲の背景(適用対象の図形よりも後ろの画像)。
  • BackgroundAlpha …… アルファチャンネルのみのBackgraundAlpha
  • FillPaint …… 対象図形のfill属性に設定された色で塗り潰した画像を入力とする
  • StrokePaint …… 対象図形のstroke属性に設定された色で塗り潰した画像を入力とする

※ BackgroundImage、BackgroundAlphaを使うには、背景に含める図形とフィルタの対象の図形の両方を含むコンテナ要素(svg、g、useなど)にenable-background="new"を指定する必要があります。

 feColorMatrixとfeOffsetの出力を1つに重ねているのがfeMergeタグです。子要素として記述したfeMergeNodeタグのin属性で指定された出力画像を、上から順番に重ねます。描画結果では、先に書いたfeMergeNodeタグの画像が奥になる点に注意してください。

 単純に重ねるのではなく、Photoshopのレイヤーエフェクトのような効果を得られるのがfeBlendタグです。mode属性に以下のいずれかを指定すると、in、in2属性で指定された出力画像をその演算方法でブレンドします。

  • normal …… feMergeと同じ
  • multiply …… inとin2を乗算する
  • screen …… 加算合成した値から乗算合成の値を減算する
  • darken …… inとin2の暗い方を採る
  • lighten …… inとin2の明るい方を採る

※ 上記の説明はあくまで概要なので、正確な計算は仕様書を参照してください。

 normalモードはfeMergeと同じなので、他の4つのブレンド方法を実装したデモを以下に示します。

フィルタリング結果のブレンド フィルタリング結果のブレンド 実際のサンプルを見る

 2つのフィルタリング結果に対して集合演算的な合成を行うのがfeCompositeタグです。使い方はfeBlendと似ており、in、in2属性で合成する出力画像を、operator属性で指定した方法で合成します。指定できる合成方法は以下のとおりです。

  • over …… feMergeと同じ
  • in …… inの画像のうち、in2と共通の領域のみを出力する
  • out …… inの画像から、in2と共通の領域を除いたものを出力する
  • atop …… in2の画像の上にoperator="in"の合成結果を重ねるて出力する
  • xor …… inとin2を重ねた画像から、inとin2の共通の領域を除いたものを出力する
  • arithmetic …… k1、k2、k3、k4の各属性値を係数として計算した結果を出力する

 上記のうちoverとarithmeticを除いたものを実装したデモを以下に示します。arithmeticについてはライティングの説明で取り上げます。

フィルタリング結果の合成 フィルタリング結果の合成 実際のサンプルを見る

 ライティング

 SVGフィルタのちょっと変わった機能として、feDiffuseLighting、およびfeSpecularLightingフィルタプリミティブの使い方を取り上げます。Photoshopのレイヤースタイルにある「ベベルとエンボス」のような立体的なライティングを施すものです。

 feDiffuseLightingは、入力画像をハイトマップ(アルファチャンネルに各ピクセルの高さを格納した画像)と考えて、拡散光によるライティング計算を実行します。拡散光とは、光沢のない質感をモデル化した計算方法で、布やゴムなどの表現に適しています。以下に使用例を示します。

Diffuseライティング Diffuseライティング 実際のサンプルを見る

 先頭にあるfeGaussianBlurは、feDiffuseLightingの入力となるハイトマップを生成するものです。正確な計算とはいえませんが、輪郭をぼかしたシルエット画像をハイトマップに見立てるわけです。feDiffuseLightingタグの属性は、それぞれ以下の意味を持っています。

  • surfaceScale …… ハイとマップの高さ係数(値が大きいほど高低差も大きくなる)
  • diffuseConstant …… 光源の光の強さ
  • lighting-color …… 光源の色

 光源となるライトの情報は、feDiffuseLightingタグの子要素として記述します。ライトにはfeDistanceLight、fePointLight、feSpotLightの3種類がありますが、ここでは最も直感的なfePointLightを使用しています。属性はライトの座標です。

 feDiffuseLightingの結果は、feCompositeのoperator="arithmetic"を利用して元の画像と合成します。合成方法がarithmeticの場合、feCompositeタグにはk1〜k4の4つの属性を指定でき、以下の計算式で合成を行います。

result = in*in2*k1 + in*k2 + in2*k3 + k4

 拡散光の場合、物体の色とライティング結果は乗算するのが普通です。従って、k1の値は1にすべきです。これだけでは光の当たっていない部分が真っ黒になるので、k3に適当な値(ここでは0.5)を指定して、環境光をエミュレートします。k2、k4の項は不要なので0としてください。

 feDiffuseLightingが光沢のない質感を表現するのに対して、プラスチックや金属のような鏡面反射光(いわゆるハイライト)を計算するのがfeSpecularLightingです。基本的な記述方法はfeDiffuseLightingと同じですが、指定すべき属性が若干異なります。以下に例を示します。

Specularライティング Specularライティング 実際のサンプルを見る

 feSpecularLightingタグに指定されている属性のうち、specularConstantはdiffuseConstantと同じくライトの強さを示します。specularExponentは独自のもので、この値を大きくするとより金属的な、鋭いハイライトになります。その他の属性、およびライトの指定方法はfeDiffuseLightingと同じです。

 鏡面反射光と物体の色の合成方法は加算が普通なので、feCompositeの属性はk2とk3が1で、それ以外が0になります。2つめのfeCompositeは元画像のアルファ値で結果をマスクするためのものです。加算合成では先頭のガウシアンブラーで外に漏れたアルファ値が残ってしまうので、その影響を取り除くために必要です。

 現実のほとんどの物体では、拡散光と鏡面反射光の両方の性質を持ちます。そのため、feDiffuseLightingとfeSpecularLightingの両方を計算して結果を足し合わせれば、よりリアルなライティングになります。それを実装したのが以下の例です。

Diffuse+Specularライティング Diffuse+Specularライティング 実際のサンプルを見る

 もっとも、SVGの主な守備範囲である二次元の図形描画やイラストの世界では、そこまでのリアリティが求められることは少ないかもしれません。このため、現実の用途ではfeSpecularLightingだけで済ませてしまうことも多いようです。その場合、specularExponentとspecularConstantの値を小さくすることで質感が拡散光に近づくことを覚えておくとよいでしょう。

 Inkscapeでフィルタを作成する

 SVGフィルタの解説の最後に、まだ取り上げていないフィルタプリミティブの説明を……といきたいところですが、それがなかなか難しいことです。SVGのフィルタプリミティブは前述のとおり低レベルな処理を行うものなので、望みの処理を記述するには画像処理の知識が必要です。そこで、少し裏技的な方法をご紹介しましょう。オープンソースのグラフィックツールであるInkscapeを利用します。

 Inkscapeの最大の特徴は、ツール自体がSVGをベースに作られていることです。標準のファイルフォーマットがSVGであるだけでなく、ツール上でSVGのXMLコードを閲覧・編集したり、属性を直接変更することもできます。多数実装されているフィルタも、すべてSVGフィルタによって実現されています。

Inkscapeのフィルタメニュー Inkscapeのフィルタメニュー

 このため、Inkscape上で適当な図形にフィルタをかけて保存すれば(保存形式は「プレーンSVG」にしてください)、そのファイル中にSVGのフィルタ定義がそのまま含まれています。それをコピー&ペーストすれば、自分のSVGファイルでも同じ効果が利用できるというわけです。

 また、InkscapeにはSVGフィルタを編集するエディタもあるので、フィルタプリミティブの効果を確かめつつ、オリジナルのフィルタを作れます。

Inkscapeのフィルタエディタ Inkscapeのフィルタエディタ

 フィルタ関連以外でも、InkscapeはSVGを扱う上でとても便利なツールです。公式サイトではWindowsやMac OS Xのバイナリも配布されており、容易にインストールできるので、ぜひ使ってみてください。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。