フーノページ



CSSアニメーション

SVG、feComponentTransferフィルターで色変換。










syntax。

<filter id = "「「5component」」">
<feComponentTransfer">
    <feFuncR type = "「「1identity/linear/gamma/discrete/table」」" ../>
    <feFuncG ../>
    <feFuncB ../>
    <feFuncA ../>
</feComponentTransfer>  
</filter>
「「3<!--プレゼンテーション属性-->」」
<rect filter = "url(「「5#component」」)" ../>
「「3/*CSS*/」」
#elm {filter:url(「「5#component」」)}


RGB毎に色を変換。


こんにちは、「ふ」です。
今回はSVGのfeComponentTransferフィルターについて紹介します。



feComponentTransferは色変換をおこなうフィルターで、R/G/B成分およびAlpha(不透明度)の4つを個別に操作することができます。
なお単なる色成分の「上げ下げ」ではなく、関数を設定→その法則に従って入力値を変換する仕組みです。

feComponentTransferは前回のfeMorphologyに比べると、かなり複雑な設定を要します。
今回は前編としてその仕組みと、基本的な関数について紹介していきます。
仕組みからしっかりと理解することで、コピペに留まらない応用力を身につけましょう。





色変換の仕組み。


feComponentTrasferを使っていくにあたり、その色変換の仕組みを知っておきましょう。


PCやスマートフォンの画像は、色の付いたpx(画素)の集まりで表示されています。色付きの四角い「つぶつぶの集まり」と考えても差し支えありません。


その1つ1つのpxは、R(red)/G(green)/B(blue)の3つの色成分の値によって、色が決定されます。


では1つのpxにおいて、R(red)値だけを考えてみましょう。いまこのpxのR値は「50」になっています。


このR値を入力したバヤイ。
ディスプレイの補正機能などが作用していない限り、R=50なのでそのままR=50として出力されます。当たり前ですが。

同様に、R=100が入力されたらR=100として表示されます。R=10なら出力もR=10。


この「あたりまえの関係」は横軸を入力、縦軸を出力とすると、⬆︎のようなグラフで表すことができます。

これを関数式で表すとすると、

out = in

となりますね。


それではinとoutの関係が、⬆︎のようになっているとどうでしょうか。
関数式で表すと、

out = in*2

です。入力したR値を2倍したものが出力のR値となります。


このinとoutの関係を、さきほど抽出したpxに施してみます。
緑のほうはわずかに明るくなっただけですが、青のほうはほぼ紫色になりました。


px単位のR値をいじった結果、はじめにあった緑と青の輪っか画像は⬆︎のように変化します。

各pxの持つR成分の値はそれぞれ違っています。個々の色成分の値を、指定した関数に基づいて変換・出力させるのがfeComponentTransferフィルターです。


ここではR値を使った例を紹介してきましたが、入力-出力の関係はR/G/B値及びA値(不透明度)、それぞれを個別に操作することが可能です。

⬆︎の2つの画像、左はG値の出力を極端に下げたもの。また右はR値の出力を思い切り上げたものです。いづれも元画像は緑と青の輪っかを使っています。



0〜1で扱う。


なおfeComponentTransferの指定では、RGB値は0〜1の範囲に置き換えたものを使用します。0〜1を255分割することになるので、例えばG=100だと100/255ということになります。

通常webやCG制作においてはRGB値は0〜255の範囲で扱うことが多いので、少し戸惑うかもしれません。ただfeComponentTransferでは入力と出力の関係を指定するだけなので、

「R = 100だから、100/255は .. 」

といっためんどくさい算出は要求されませんので、ご安心を。




記述方法。


<svg>
<filter id = "「「5component1」」">
    <「「1feComponentTransfer」」><「「1/feComponentTransfer」」>
</filter>
</svg>

SVG内に<filter>要素で、フィルターコンテナを定義します。参照用のidは「component1」としておきました。
その中に<feComponentTransfer>要素を配置。ただしこれだけではフィルターを掛けたとしても、何も起きません。この中に更に、R/G/B/Aごとの入力-出力操作を定義していく必要があります。

<filter id = "component1">
    <feComponentTransfer>
        <「「1feFuncR」」 type = "...."/>
        <「「1feFuncG」」 type = "...."/>
        <「「1feFuncB」」 type = "...."/>
        <「「1feFuncA」」 type = "...."/>
    </feComponentTransfer>
</filter>

RGBAそれぞれの出力状態を操作するのが、<feFuncR> <feFuncG> <FeFuncB> <feFuncA> 要素です。これらの要素を <feComponentTransfer> 要素の入れ子にすることで、はじめてフィルター効果を働かせることができます。

type属性。


<feFuncR type = "「「1linear/identity/gamma/discrate/table」」"/>

feFuncR/G/B/A要素にはtype属性を持っています。
値には5種類が用意されており、入力-出力の関係操作時に使用する、関数のタイプを指定することができます。

・linear :直線を使った一次関数
・identity :入力 = 出力とする恒等変換
・gamma :ガンマ曲線による指定
・discrate :階段(ステップ)関数
・table :折れ線による指定

前編である本記事では基本的なlinear関数、identitiy関数について紹介していきます。その他の関数については後編でお伝えします。




linear関数。


feFunc「「2X」」 type = "linear"
        slope = "数値" (初期値:1)
        intercept = "数値" (初期値:0)


linear関数は、直線による一次関数です。
入力に対して、n倍に変換されたものが出力となります。


linear関数の傾きはslope属性(数値)で指定します。
初期値は1で、入力を等倍したものが出力に返されます。


直線は必ずしも原点を通るとは限りません。通過しない場合、原点からの垂直距離を切片といいます。
切片の指定はintercept属性(数値)で指定します。intercept属性の初期値は0で、指定がない場合には直線は原点を通過します。

<img src = "sleep.png">

◾️ sleep.png

気持ちよく寝ているところ申し訳ないのですが、この子でlinearを試してみましょう。


slope(傾き)。

G(green)成分の入出力を操作してみます。 直線の傾きを0.5とすると、入力に対して半分の値が出力されるようになります。

<svg style = "position:fixed;">「「1 ..@1@」」
    <filter id = "「「5linear01」」">「「1 ..@2@」」
        <feComponentTransfer>
        <feFuncG type = "linear" slope = "「「10.5」」"/>「「1 ..@3@」」    
        </feComponentTransfer>    
    </filter>
</svg> 

@1@ <filter> 要素は、<svg> 要素の中に記述する必要があります。ただしHTMLの <body> 内に <svg> を定義した場合、自動的に300*150pxのSVG領域が確保されてしまいます。
そのためposition : fixedとし、HTMLのレイアウトに影響を与えないようにしています。

@2@ filterコンテナを配置。idは「linear01」としました。

@3@ G成分を操作するので、<feFuncG> を記述しています。傾きをslope属性で「0.5」としました。


<img src = "sleep.png" style = "filter:url(「「5#linear01」」)">

<img>要素の中で定義したフィルターを呼び出します。



結果、全体的に赤みが掛かったよう⬆︎になりました。
G(green)成分の傾きを0.5としたので、G値だけが入力に対して半分の出力で描画されたのです。他のR値やB値はそのまま等倍で返されているので、「赤みが強まった」というよりは「緑が弱められた」というのが実際です。


今度は傾きを「2」とし、入力に対し2倍の出力を返すようにしてみます。

<feComponentTransfer>
    <feFuncG type = "linear" slope = "「「12」」"/> 
</feComponentTransfer>      



G成分が入力値に対し、2倍の出力値を返すようになりました。
その結果、緑っ気が強まりましたね。


ところでslopeを「2」としたとき、グラフの矢印の部分はどのように扱われるのでしょうか?


feComponentTransferでは、値が0〜1の範囲からはみ出したときは強制的に0または1に丸められます。なので実際のグラフは⬆︎のようになります。
通常RGBを扱うときにも、0未満や255より大きい値はありえないですもんね。

いまのサンプルで言うと、G成分が0.5以上のpxはG = 1.0として出力されたことになります。



intercept(切片)。


intercept(切片)を指定すると、全体の出力値が「底上げ」されます。


先づはinterceptなしのフィルターを掛けてみます。今回はfeFuncBを使って、B(blue)成分を操作します。

<svg style = "position:fixed;">
    <filter id = "linear01">
        <feComponentTransfer>
            <feFuncB type = "linear"
            slope = "「「10.5」」"/> 
        </feComponentTransfer>    
    </filter>
</svg> 

slopeを「0.5」としたので、青色成分は入力に対し半分の出力値が採用されます。



B(blue)成分が弱くなったため、R(red)成分とG(green)成分が強調されることになります。その結果、全体が黄色に寄せられました。

<feComponentTransfer>
    <feFuncB type = "linear"
    「「1intercept = "0.5"」」
    slope = "0.5"/> 
</feComponentTransfer>     

ここにinterceptを加えてみます。値は「0.5」としました。


指定した内容をグラフにしたもの⬆︎。
入力値が最小の0であっても、0.5として出力されます。傾き0.5のグラフ全体が「底上げ」される形になります。



結果は予想通り、全体的に青成分が強まりました。
slope(傾き)を大きくしたときよりも顕著です。


slope属性には負の値を指定することも可能です。
傾きを「-1」とし、切片を「0.5」にしてみます。

<feComponentTransfer>
    <feFuncB type = "linear"
    intercept = "0.5"
    slope = 「「1"-1"」」/> 
</feComponentTransfer>     



青成分が弱いところは青が強調され、青成分が強いところは黄色っぽくなりました。
インパクトある色合いになりましたね。



idetity関数。


前述のとおりfeComponentTransferには、<feFuncR> <feFuncR> <FeFuncB> <feFuncA> という4つのチャンネルが用意されています。

<feComponentTransfer>
    <feFuncG type = "linear" slope = "1.5"/> 
</feComponentTransfer>     

ここで<feFuncG>を操作したとします。
そのとき記述していない他の <feFuncR> <FeFuncB> <feFuncA> チャンネルのtype属性には、初期値である「identity」関数が採用されます。
identitiyは「恒等式(つねに等しい式)」という意味を持ちます。


identity関数をグラフにすると原点を通る傾き「1」の直線となり、これはin値に対して全く同じout値を返す状態です。
つまり初期値であるidentityが採用されているチャンネルは「何もしない」ということです。

<feComponentTransfer> 
    <feFuncR type = identity/>
     <feFuncG type = "linear" slope = "1.5"/>
    <feFuncB type = identity/>
    <feFuncA type = identity/>
</feComponentTransfer>     

feFuncG以外のチャンネルを敢えて記述するとしたら、⬆︎のようになります。



Alpha値について。


<feComponentTransfer>
    <feFuncA ..../>    
</feComponentTransfer>    

feComponentTransferでは、RGBの他にA(Alpha)値を操作することもできます。
ここでは <feFuncA> を使って、A値を操作した場合の特性について紹介していきます。


Alpha値とは各ピクセルの色情報(R/G/B)に加えて、不透明度の情報を持たせたものです。
CSSの色指定でRGBAを使ったことのある方もいるかと思います。最小値は「0」で完全な透明、最大値は「1」で完全な不透明となります。

feComponentTransferではfeFuncAを使うことで、Alpha値の操作が可能です。ただしRGB情報のない部分は透明な黒として扱う、という性質をもっており、使用する際には配慮が必要です。
以下、試していきましょう。



<feFuncA>は不透明度を操作します。
様子を観察しやすいよう、背景色をつけてお送りします。


<filter id = "linear01">
    <feComponentTransfer>
    <「「1feFuncA」」 type = "linear" slope = "0.5" /> 
    </feComponentTransfer> 
</filter>


Aチャンネルに対して傾き「0.5」の変換を施しました。



結果、全体的に半透明になりました。
サンプルに使っている画像。構成しているpxのAlpha値はすべて最大の「1」です。0.5の傾きを指定することにより、すべてのpxのAlpha値が「0.5」として出力されています。



謎の枠線。

ここで、関数にinterceptを追加してみます。

<filter id = "linear01">
    <feComponentTransfer>
    <feFuncA type = "linear" slope = "0.5" intercept = "「「10.3」」" /> 
    </feComponentTransfer> 
</filter>




!?
画像部分はいいとして、なんだか謎のborderが付いています。



<filter filterUnits = "「「1objectBoundingBox」」">
</filter>

<filter>には、フィルター効果の描画範囲を指定するfilterUnits属性というものがあり、初期値は「objectBoundingBox」となっています。


初期値の場合、要素を囲むボックスの120%の範囲にわたってfilter効果が描画されます。


要素に掛けるfeComponentTransferを、linear関数で傾き0.5、切片0.3としたとき。


画像の部分はすべてのpxがRGB情報を持っており、A値(不透明度)は1になっています。指定された関数によって変換すると1*0.5+0.3 = 0.8。RGB値はそのままで、不透明度0.8で出力されます。



そして外側の10%の部分。ここは透過部分なので、RGBの情報がありません。


RGB情報を持たないpxは、RGBA(0,0,0,0)。つまり透明度0の黒として扱われます
Alpha値を上げていくと、徐々に不透明な黒で描画されていくようになります。


完全不透明の黒に対して、A値を変換したものが出力されます。




外側10%のRGB情報がない部分はAlpha = 0*0.5+0.3 = 0.3となり、不透明度0.3の黒で描画されてしまっていたのでした。

interceptを使って出力値を底上げする場合は、外側10%の部分も影響を受けてしまうので注意してください。



linearだけでも楽しい。


最後までお読みくださり、ありがとうございました。

今回はfeComponentTransferの前編として、その構造とシンプルなlinear関数を取り上げてきました。これだけでも豊富な色変化が表現できますね。
次回はそれ以外の関数。gamma、table、discrete関数について紹介します。

ではまた〜 🎵




「ふ」です。

ふ

ベクターグラフィック、web、ガジェットなど。役立つ情報や観ていてたのしいページを書いていきたいと思います。