2012年3月27日火曜日

svgのフィルターを使ってワープ?っぽい効果をつける

feDisplacementMapを理解する為にワープっぽい効果を作ってみた.実用度はあるのか判らない.feDisplacementMapは種がわかれば単純なのに,サンプルが少なくて理解しにくいんだよね.

feDisplacementMapは元となる画像を変形して様々な効果をつけるものだが,実際に思った通りの効果をつけようとすると非常に面倒.というのもピクセルの移動量を色の濃淡で定義するためだ.単純なものであればsvgのもつグラデーション機能などを使えばよいが,限界もある.

従ってピクセル単位で色の指定が可能なcanvasを使って対処するのが最も汎用的であろうと考えた.事前に画像ソフト等でファイルを用意する手もあるが,javascriptでさっくりと画像を作れてしまうのは魅力.
  1. 画像に付けたい効果を思い浮かべる.
  2. ピクセルごとの移動量,移動方向を考える.
  3. 2で得た移動量を水平方向と垂直方向に分離する.
  4. 3で得た内容を元にcanvasにイメージを描画する.
    ピクセルごとの移動量を色の濃さ:0〜255にマッピングする.(128は移動しないことを表す)
    水平方向はredで垂直方向はgreenで表現する.(blueとalphaは無視してよい.)
  5. この時点で画像ファイルを保存する,もしくはgetDataURLでURL文字列として画像を取得する.
  6. svgのfeDisplacementMap要素において5の画像を参照する.
まあこのような流れでやればなんとか応用できるかと思う.ただこのフィルターはfilterRes属性の値の影響を直に受けるようなので,細かなチューニングが必要.数少ないsvgとcanvasとの連携の例と言えるんじゃないか.
 ×

svgの内容.
<?xml version="1.0" standalone="no"?>
<svg width="200px" height="200px" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
 <defs>
  <filter id="feDisplacementMap" image-rendering="optimizeSpeed" color-interpolation-filters="sRGB" filterRes="1000,1000">
   <feImage xlink:href="http://www.h2.dion.ne.jp/~defghi/svgWarp/gradient.png" x="0" y="0" width="200" height="200" result="map"/>
   <feDisplacementMap in="SourceGraphic" in2="map" xChannelSelector="R" yChannelSelector="G" scale="140"/>
  </filter>
 </defs>
 <image filter="url(#feDisplacementMap)" 
  xlink:href="http://www.konami.jp/products/dl_xbox_dracula_hd/images/charlotte.jpg" 
  width="200" height="200"/>
</svg>


canvasによる画像の生成.
中心から外側に向かうピクセル変異を表現するため,角度毎に色を設定するようにした.kの値を1に変更すると何をしたかったのか判ると思う.
<!DOCTYPE html>
<html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
 <style type="text/css">
 </style>
 <script type="text/javascript">
 window.onload = function(){

  var canvas = document.getElementById("grad");
  var ctx = canvas.getContext("2d");
  var id = ctx.createImageData(800, 800)
  var d = id.data;
  for(var y = 0; y < 800; y++){
   for(var x = 0; x < 800; x++){
    var deg = getDeg(x, y);
    var dist = getDistance(x, y);
    var k = Math.max(dist - 300, 0) / 300;

    var pos = (x + 800 * y) * 4;
    //R    
    d[pos + 0] = 128 + 128 * Math.cos(deg) * k;
    //G
    if(x == 400 && y < 400){
     //境界調整
     d[pos + 1] = 128 + 128 * k;
    }else{
     d[pos + 1] = 128 + 128 * Math.sin(deg) * k;
    }
    //B
    d[pos + 2] = 0;
    //A
    d[pos + 3] = 255;
   }
  }
  ctx.putImageData(id, 0, 0);

  function getDeg(x, y){
   var _x = x - 400;
   var _y = y - 400;
   var deg = _x != 0 ? Math.atan(_y/_x): Math.PI/2;
   return _x < 0 ? deg: deg + Math.PI;
  }

  function getDistance(x, y){
   return Math.sqrt(Math.pow(x - 400, 2) + Math.pow(y - 400, 2));
  }
 };
 </script>
 </head>
 <body>
  <canvas width="800px" height="800px" id="grad"/>
 </body>
</html>

0 件のコメント:

コメントを投稿