2012年6月6日 星期三

fisheye effect using DisplacementMapFilter

flash 要做出魚眼效果,在 flash8 以前的時代是非常痛苦的事,
不過現在有了 DisplacementMapFilter 這件事比變得簡單多了,
另外還有 Pixel Bender 也可以做到魚眼效果,但這兩個的原理不太一樣,
我自己的測試,Pixel Bender 的效能更好,但要 flash player 10 後
才有支援。


首先,Pixel Bender 與 flash DisplacementMapFilter 處理方式不太一樣
DisplacementMapFilter 是把來源圖片上面各個像素按照一個 map 來移動到新的位置
然後拼出一張新的圖。

而 Pixel Bender 是定義 "每一個來源圖片像素" 的執行函數,
所以如果我定義 "輸出結果的像素是來源圖片像素的 -2 相對位置",
那麼結果就是目標圖片相當於,把來源圖片往右邊移動兩個像素,
兩者原理不太一樣。

回到本篇重點 DisplacementMapFilter
DisplacementmapFilter 藉由讀取參考用的 BitmapData 資料,
把來源圖片的像素搬移(置換)位置,每個像素搬移的方式不同就會造成 "圖片的扭曲效果",
這邊說明一下濾鏡除了可以套用在 BitmapData 物件上(如上面所述),也可以直接套
用於顯示物件,像是 Bitmap 實體。

參考用的 BitmapData 只要參考它的 Red channel 就夠了,
因為一般參考用 BitmapData 習慣設成灰階的),

就像一般 filter 一樣
DisplacementMapFilter 也有建構式:

DisplacementMapFilter(mapBitmap:BitmapData, mapPoint:Point, componentX:Number,
                             componentY:Number, scaleX:Number, scaleY:Number,
                             [mode:String], [color:Number], [alpha:Number])

mapBitmap 參考用的 BitmapData
mapPoint 從 mapBitmap 的哪個像素開始參考
(假如你從 (5, 5) 開始參考一個100X100的 mapBitmap,那 mapBitmap 的尺寸就是 95X95)
componentX/componentY 決定像素位移的時候要參考 mapBitmap 像素的 color channel
scaleX/scaleY 決定 "位移百分比(0.0~1.0)" 之後要乘上的倍率

mode/color/alpha 比較複雜且不重要,可以不要寫。

範列如下
var displaceFilter:DisplacementMapFilter;
displaceFilter = new DisplacementMapFilter(fisheyeLens,
                                new Point(radius, 0),
                                BitmapDataChannel.RED,
                                BitmapDataChannel.GREEN,
                                radius, 0);
srcBitmap.filters = [displaceFilter];

這邊 fisheyeLens(魚眼) bitmapdata 為參考影像,其中像素的顏色都會用來當做是
對置換函數的輸入;
也就是說,參考影像內的某些 x, y 座標會決定要在來源影像內的該 x,y 座標上
套用多少置換量 (在適當位置的實體移動)。

置換函數的公式:
dstPixel[x, y] = srcPixel[x + ((componentX(x, y) - 128) * scaleX) / 256, y + ((componentY(x, y) - 128) * scaleY) / 256]

DisplacementMapFilter 可以搭配 Perlin noise(BitmapData)
BitmapData 支援兩種雜訊效果,noise/perlinNoise
perlinNoise 適用於有組織的紋理,像是煙霧、雲、水、火焰甚至是爆炸效果。


原理講完了,進入到 code 的部份。

// 顯示的原始圖像
image = new ImageClass();
addChild(image);
// 建立參照影像,這裡是參照紅綠兩種顏色
// 參照影像-魚眼,RADIUS = 100
fisheyelens = createFisheyeMap(RADIUS);
// 建立 dmf,參照紅綠兩種顏色
dmf = new DisplacementMapFilter(fisheyelens,
new Point(RADIUS, 0),
BitmapDataChannel.RED,
BitmapDataChannel.GREEN,
RADIUS * 2, RADIUS * 2);
// add filters
image.filters = [dmf];
addEventListener(Event.ENTER_FRAME, drawEffect);

function drawEffect(e:Event = null):void
{
  // following mouse position
  dmf.mapPoint = new Point(stage.mouseX - RADIUS, stage.mouseY - RADIUS);
  // apply filter
  image.filters = [dmf];
}

function createFisheyeMap(radius:int):BitmapData
{
  var d:Number = RADIUS << 1;
  //參照紅綠:0x808000
  var circle:BitmapData = new BitmapData(d, d, false, 0x808000);
  var distance:Number;
  var cx:int, cy:int, newX:int, newY:int;
  var base:Number, t:Number, dx:Number, dy:Number;
  var red:uint;
  var blue:uint;
  var green:uint;
  for (cy = 0; cy < d; cy++)
  {
    for (cx = 0; cx < d; cx++)
    {
      newX = cx - radius;
      newY = cy - radius;
      distance = Math.sqrt(newX * newX + newY * newY);
      if (distance < radius)
      {
        base = Math.sin(Math.PI / 2 * distance / radius);
        // 值愈大扭曲的愈厲害,預設 INTENSITY = 1
        t = Math.pow(base, INTENSITY);
        dx = newX * (t - 1) / d;
        dy = newY * (t - 1) / d;
        red = 0x80 + dx * 0xFF;
        //參照紅綠, 拿掉藍
        //blue = 0x80 + dx * 0xFF;
        green = 0x80 + dy * 0xFF;
        circle.setPixel(cx, cy, red << 16 | green << 8 | blue);
      }
    }
  }
  return circle;
}

[ref]
http://help.adobe.com/zh_TW/as3/dev/WS5b3ccc516d4fbf351e63e3d118a9b90204-7d61.html

沒有留言:

張貼留言