Translate

2020年12月23日 星期三

有趣的 SVG filter - feDisplacementMap

feDisplacementMap 是個很有趣的東西,可以用一張平面的圖把另一張平面的圖做扭曲變形,第一次看到時覺得非常驚豔,以往只能等美術人員畫好的圖,原來可以只靠濾鏡做到,也想不到怎麼做的,是用了什麼神奇的方法來描述整個變形,直到看到這個公式才大概理解整個做法。

參考資料:這裡

P'(x,y) ← P( x + scale * (XC(x,y) - .5), y + scale * (YC(x,y) - .5))

先來看一個範例

灰色是本來的圖,紅框是只有紅色在左上角的濾鏡,

當座標偏移都用綠色時,因為綠色=0 所以不會偏移, 本來的 座標 (x,y) 會變成 ( x + scale * (-0.5) , y + scale * (-0.5) ) ,

當切換到 R 時,(x,y) 理論上會是 ( x + scale * (0.5) , y + scale * (0.5) ) , 所以圖形往左上移動,然而實際上移動的座標並非 scale * 0.5 ,似忽和 R 的亮度值有關,

當切換到 A 時,這裡的設定是 7f ,也就是 0.5 , 於是圖形就回到原處沒有偏移

*實測結果 Firefox 會不正常


<svg width="300" height="360" xmlns="http://www.w3.org/2000/svg" style="background-color:#fff">
<defs>
  <filter id="a122301_f1" filterUnits="userSpaceOnUse">
    <feImage xlink:href="https://yiharng.github.io/pic_circle.jpg" x="0" y="0" width="280" height="280" result="a122301_t1"></feImage>
    <feDisplacementMap in="a122301_t1" in2="SourceGraphic" scale="40" xChannelSelector="R" yChannelSelector="R">  
      <set attributeName="xChannelSelector" to="R" begin="a122301_cr.click"></set>
      <set attributeName="yChannelSelector" to="R" begin="a122301_cr.click"></set>
      <set attributeName="xChannelSelector" to="G" begin="a122301_cg.click"></set>
      <set attributeName="yChannelSelector" to="G" begin="a122301_cg.click"></set>
      <set attributeName="xChannelSelector" to="A" begin="a122301_ca.click"></set>
      <set attributeName="yChannelSelector" to="A" begin="a122301_ca.click"></set>
    </feDisplacementMap>
  </filter>
</defs>
  <rect x="0" y="0" width="150" height="150" fill="#ff00007f" style="filter: url(#a122301_f1)"></rect>
  <rect x="0" y="0" width="150" height="150" stroke="#f00" fill="none"></rect>
  <image href="https://yiharng.github.io/pic_circle.jpg" x="0" y="0" width="280" height="280" style="opacity:0.2"></image>
  <text id="a122301_cr" x="10" y="340">Channel R</text>
  <text id="a122301_cg" x="100" y="340">Channel G</text>
  <text id="a122301_ca" x="190" y="340">Channel A</text>
</svg>
Channel R Channel G Channel A

既然知道原理,那麼就可以弄個漸層來試試效果



原圖<br>
<img src="https://yiharng.github.io/cat280.jpg" width="280"><br>

濾鏡<br>
<svg width="300" height="280" xmlns="http://www.w3.org/2000/svg" style="background-color:#fff">
<defs>
  <filter id="a122301_f2" filterUnits="userSpaceOnUse">
    <feImage xlink:href="https://yiharng.github.io/cat280.jpg" result="a122301_t2" x="-50" y="-20"></feImage>
    <feDisplacementMap in="a122301_t2" in2="SourceGraphic" scale="40" xChannelSelector="R" yChannelSelector="R" result="a122301_d2"></feDisplacementMap>
  </filter>
  <radialGradient id="a122301_rad1">
    <stop offset="0%" stop-color="#f00"></stop>
    <stop offset="100%" stop-color="#000"></stop>
  </radialGradient>
</defs>
  <ellipse cx="150" cy="140" rx="110" ry="110" fill="url(#a122301_rad1)" style="filter: url(#a122301_f2)"> 
  </ellipse>
</svg>

原圖

濾鏡

加上一些計算,可以做出水波效果


<svg width="280" height="210">
  <rect x="0" y="0" width="280" height="210" fill="url(#a122303_rad1)"></rect> 
</svg>
<svg width="280" height="210" xmlns="http://www.w3.org/2000/svg" style="background-color:#fff">
<defs>
  <filter id="a122303_f1" filterUnits="userSpaceOnUse">
    <feImage xlink:href="https://yiharng.github.io/bird.jpg" result="a122303_t1" x="-20" y="-20" width="330" height="250"></feImage>
    <feDisplacementMap in="a122303_t1" in2="SourceGraphic" scale="20" xChannelSelector="R" yChannelSelector="R" result="a122303_d1"></feDisplacementMap>
  </filter>
  <radialGradient id="a122303_rad1" cx="70%" cy="60%" r="100%">
    <stop offset="0.0" stop-color="#000">
      <animate attributeName="offset" to="0.2" dur="1s" repeatCount="indefinite"></animate>
    </stop>
    <stop offset="0.1" stop-color="#f00">
      <animate attributeName="offset" to="0.3" dur="1s" repeatCount="indefinite"></animate>
    </stop>
  </radialGradient>
</defs>
  <rect x="0" y="0" width="280" height="210" fill="url(#a122303_rad1)" filter="url(#a122303_f1)"></rect> 
</svg>
<script>
$(()=>
{
  let hh="";
  let i,j="f";
  let k=0.03;
  for (i=0;i<1;i+=k)
  {
    if (j=="0") j="f"; else j="0"
    hh+='<stop offset="'+(i.toFixed(2))+'" stop-color="#'+(j)+'00">'
       +'<animate attributeName="offset" to="'+((i+k*2).toFixed(2))
       +'" dur="1s" repeatCount="indefinite"></animate></stop>';
  }
  $("#a122303_rad1").html(hh);
})
</script>  

修改一下波的行徑方向,可以變成倒影,而這個範例卻又發現了 chrome 的 bug

Firefox 是本來就看不到效果,但是倒影的圖片翻轉是正常的

Chrome 很奇怪的,圖片翻轉居然沒效......花了一些時間測試,在 svg 外層包一個 div 並旋轉 0.001 度好像可以解決這個 bug , 但是在某些情況下又不行......

  <div style="transform:rotate(0.001deg)">
    <svg>
      .
      .
      .
    </svg>
  </div>

Safari 正常



<div style="position:absolute;width:290px;height:200px;background:url(https://yiharng.github.io/bird280.jpg)">
</div>
<div style="height:190px;"></div>
<div style="transform:rotate(0.001deg)">
<svg width="290" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
  <filter id="a122306_f1" filterUnits="userSpaceOnUse">
    <feTurbulence type="turbulence" baseFrequency="0.01 0.1" numOctaves="2" result="a122306_tt">
    </feTurbulence>
    <feImage href="https://yiharng.github.io/bird280.jpg" x="-20" y="-30" width="320" height="240" preserveAspectRatio="none" result="a122306_bird"></feImage>
    <feDisplacementMap in="a122306_bird" in2="SourceGraphic" scale="20" xChannelSelector="R" yChannelSelector="R" result="a122306_d1">  
    </feDisplacementMap>
    <feDisplacementMap in="a122306_d1" in2="a122306_tt" scale="20" xChannelSelector="R" yChannelSelector="G">  
    </feDisplacementMap>
  </filter>
    <linearGradient id="a122306_linear1" x1="0" y1="0" x2="0" y2="1" spreadMethod="pad">
      <stop offset="0%" stop-color="#000"></stop>
      <stop offset="10%" stop-color="#f00"></stop>
      <stop offset="20%" stop-color="#000"></stop>
    </linearGradient>
</defs>
  <rect x="0" y="0" width="300" height="300" transform="scale(1,-1) translate(0,-200)" fill="url(#a122306_linear1)" filter="url(#a122306_f1)"></rect>
</svg>
</div>

<script>
$(()=>
{
  let hh="";
  let i,j="f";
  let k=0.03;
  for (i=0;i<1;i+=k)
  {
    if (j=="0") j="f"; else j="0"
    hh+='<stop offset="'+(i.toFixed(2))+'" stop-color="#'+(j)+'00">'
       +'<animate attributeName="offset" to="'+((i-k*2).toFixed(2))
       +'" dur="2s" repeatCount="indefinite"></animate></stop>';
  }
  $("#a122306_linear1").html(hh);
})
</script>    

如果看不到水波和倒影,那麼可能你用的瀏覽器是 Firefox ,自從開始玩 SVG 後就常常發現這些瀏覽器有不少東西不支援或者支援一半。

Firefox 並不是不支援 feDisplacementMap 而是無法以及時畫的圖給濾鏡使用,以下範例用的是 feTurbulence 產生的圖,在所有瀏覽器都支援



<svg width="300" height="400" xmlns="http://www.w3.org/2000/svg">
<defs>
  <filter id="a122305_f1" filterUnits="userSpaceOnUse">
    <feTurbulence type="turbulence" baseFrequency="0.02 0.1" numOctaves="2" result="a122305_tt">
      <animate attributeName="baseFrequency" values="0.02 0.1;0.022 0.13;0.02 0.1" dur="15s" repeatCount="indefinite"></animate>
    </feTurbulence>
    <feDisplacementMap in2="a122305_tt" in="SourceGraphic" scale="40" xChannelSelector="R" yChannelSelector="G">  
    </feDisplacementMap>
  </filter>
</defs>
  <image href="https://yiharng.github.io/bird280.jpg" x="-20" y="0" width="320" height="200" preserveAspectRatio="none" style="transform:translate(0,430px) scale(1,-1) ;filter: url(#a122305_f1)"></image>
  <image href="https://yiharng.github.io/bird280.jpg" x="-20" y="0" width="320" height="240" preserveAspectRatio="none"></image>
</svg>

沒有留言:

張貼留言