【AS3 Coder】任務四:噪音的魅力(中)

若是把Math.random方法做爲一個生成隨機數字的辦法,那麼bitmapData.perlinNoise就是一個生成隨機顏色的辦法。在這一部分的對於噪聲的應用介紹文章中咱們一塊兒來看看使用柏林噪聲的隨機化像素功能能爲咱們完成什麼樣的隨機化效果。html

本文章源碼下載:www.iamsevent.com/zb_users/upload/AS3Coder4/AS3Coder4_2.rarweb

 

滲透型溶解效果算法

 

想必列位在看PPT或者一些視頻的時候常常會看到溶解效果,那麼在AS3中,BitmapData也提供了一個能夠實現溶解效果的方法:threshold。先來一塊兒看看這個方法如何使用。編程

public function threshold(sourceBitmapData:BitmapData,  sourceRect:Rectangle,  destPoint:Point,  operation:String,  threshold:uint,  color:uint  = 0, mask:uint  = 0xFFFFFFFF, copySource:Boolean  = false):uint數組

 

根據指定的閾值測試圖像中的像素值,並將經過測試的像素設置爲新的顏色值。 經過使用 threshold()  方法,您能夠隔離和替換圖像中的顏色範圍,並對圖像像素執行其它邏輯操做。less

threshold() 方法的測試邏輯以下所示:dom

  1. 若是 ((pixelValue & mask) operation (threshold &  mask)),則將像素設置爲 coloride

  2. 不然,若是 copySource == true,則將像素設置爲 sourceBitmap  中的對應像素值。函數

operation 參數指定要用於閾值測試的比較運算符。 例如,經過使用「==」做爲  operation 參數,您能夠隔離圖像中的特定顏色值。 或者經過使用 {operation: "<", mask:  0xFF000000, threshold: 0x7F000000, color:  0x00000000},您能夠將全部目標像素設置爲在源圖像像素的 Alpha 小於 0x7F 時是徹底透明的。  您能夠將此技巧用於動畫過渡和其它效果。學習

 

參數

sourceBitmapData:BitmapData  — 要使用的輸入位圖圖像。 源圖像能夠是另外一個 BitmapData 對象,也能夠引用當前 BitmapData 實例。

sourceRect:Rectangle  — 定義要用做輸入的源圖像區域的矩形。

destPoint:Point  — 目標圖像(當前 BitmapData 實例)中與源矩形的左上角對應的點。

operation:String  — 下列比較運算符之一(做爲字符串傳遞):「<」、「<=」、「>」、「>=」、「==」「!=」

threshold:uint  — 測試每一個像素時要比較的值,以查看該值是達到仍是超過閾值。

color:uint  (default = 0) — 閾值測試成功時對像素設置的顏色值。 默認值爲 0x00000000。

mask:uint  (default = 0xFFFFFFFF) — 用於隔離顏色成分的遮罩。

copySource:Boolean  (default = false) — 若是該值爲  true,則源圖像中的像素值將在閾值測試失敗時複製到目標圖像。 若是爲  false,則在閾值測試失敗時不會複製源圖像。

上面是AS3語言參考手冊中對threshold方法的一個描述,這個方法的工做原理被總結爲一句

若是 ((pixelValue & mask) operation (threshold & mask)),則將像素設置爲 color

這句話是什麼意思呢,先來學習一下計算機編程中邏輯與(&)運算符的運算規則吧,當兩個數進行邏輯與運算時,會將這兩個數轉換爲二進制數,以後對兩個二進制數進行逐位邏輯與運算最終得出結果。例如存在兩個二進制數10101和11000,它們進行邏輯與運算的結果是(邏輯與運算法則:0&1=0, 1&1=1,1&0=0,0&0=0,即二者間有0則運算結果爲0):

10101

&      11000

----------------------

10000

那麼對於一個顏色值來講咱們一般使用一個十六進制的數來表示,一個十六進制的數等於4位二進制(由於16等於2的四次方),所以,一個0xFF00FF的顏色轉換成二進制就是

11111111 00000000 11111111

那麼讓這個顏色值與與一個0xFFFFFF的十六進制值進行邏輯與運算的結果就是

11111111 00000000 11111111

&      11111111 11111111 11111111

-----------------------------------------------------

11111111 00000000 11111111

結果就是其自己。若是讓這個顏色去和另外一個十六進制數0x00FFFF進行邏輯與的話咱們會發現運算結果就是0x0000FF。通過上面的兩個計算咱們明白,當一個十六進制的顏色值與另外一個十六進制數進行邏輯與運算的時候,這另外一個數(稱其爲與數)中若某一位爲0則原顏色值中的對應位也會被置爲0,就像以前的例子,0xFF00FF和0x0000FF邏輯與的結果是0x0000FF,由於與數的前四位都是0,因此把被與數的前四位也都帶成了0。那麼對一個顏色值進行邏輯與運算有什麼用呢?咱們知道,一個十六進制的顏色值0xFFFFFF能夠當作是三種顏色:RGB(Red:紅,Green:綠,Blue:藍)的組合,每兩位組成一個顏色,因此一個顏色值爲0x123456的顏色事實上是由亮度分別爲十二、3四、56的紅、綠、藍三色組合而成的。所以,若是咱們要對一個顏色值中的R/G/B某一個顏色值進行改變或運算的話就可使用一個mask(掩碼)來取出某一個顏色值。好比我要對0x123456這個顏色的B(藍色)進行操做的話就須要讓它與一個值爲0x0000FF的掩碼進行邏輯與運算,此運算會讓掩碼爲0的位把原顏色值的相應位也帶成了0,而值爲F的位就保留原值,因此運算結果是0x000056,這樣就只留下了B的值,便於咱們對B顏色進行一系列運算而不會牽扯到另外兩個顏色。

瞭解了邏輯與的運算方式後我們再回頭看看((pixelValue & mask) operation (threshold & mask))這個算式,當咱們傳入一個掩碼後,該掩碼會分別與pixelValue以及threshold 的值進行邏輯與操做,若是我傳入的mask值爲0x0000FF,那麼pixelValue及threshold的值與mask進行邏輯與運算後結果僅留下B(藍)顏色的值,此時再比較這兩個B值(如何比較則取決於operation的值)就能夠獲得最終結果。假設此時mask值仍是0x0000FF,pixelValue和threshold的顏色值分別爲0x123456和0x654321,operation的值爲">=",那麼此時進行顏色測試:

( 0x123456 & 0x0000FF ) >= ( 0x654321 & 0x0000FF )         ——>        0x000056 >= 0x000021        ——>        true(經過)

假設剛纔的0x123456這個顏色所在位置是(0,0)點,當經過顏色測試後,顏色所在位置(0,0)點的顏色會被設置爲threshold方法第六個參數(color)的顏色值,若我傳入的color參數爲0x00000000,即徹底透明的黑色,那麼(0,0)點的顏色會變成透明,誰叫它這點的顏色經過了顏色測試呢……

好吧,接下來讓咱們一塊兒作個練習。我有一個bitmapData的對象bmd,要讓它藍色值大於一半亮度(0x80)的像素位置顏色變成透明,就能夠這麼作:

bmd.threshold(bmd, bmd.rect, new Point(). ">", 0x80, 0x00000000, 0x0000FF)

第一個bmd是threshold方法的調用者,也就是使用threshold方法進行像素顏色改變後的受影響者;第二個bmd是threshold方法的第一個參數,也就是threshold方法的像素測試源。因此上面這條語句就是對bmd中的所有顏色進行像素測試,並把測試結果着色於bmd自己,上面語句的運行結果就是和以前咱們所預期的同樣:藍色值大於一半亮度的像素位置顏色變成透明的了。

好吧,理論知識已經講得差很少了,接下來來看一個實戰例子,讓咱們的threshold方法用得更有意義一點,先看效果(點擊圖片在線瀏覽,下同):

這個效果就是一個簡單的溶解效果,每幀溶解1%的像素,100幀後將圖片徹底變成透明,實現它的代碼至關簡單:

package
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	
	[SWF(width="400",height="600")]
	public class SimpleDissolve extends Sprite
	{
		[Embed(source="assets/fungus.jpg")]
		private var sourceImg:Class;
		private var sourceBMD:BitmapData;
		
		private var bmp:Bitmap;
		private var bmd:BitmapData;
		private var currentPercent:Number = 1;
		private var point:Point = new Point();

		
		public function SimpleDissolve()
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			
			sourceBMD = new sourceImg().bitmapData;
			
			runPostImageLoad();
		}
		
		protected function runPostImageLoad():void 
		{
			bmp = new Bitmap();
			bmp.bitmapData = sourceBMD.clone();
			addChild(bmp);
			stage.addEventListener(MouseEvent.CLICK, onClick);
			
		}
		
		protected function onClick(event:Event):void
		{
			bmp.bitmapData = sourceBMD.clone();
			bmd = bmp.bitmapData;
			currentPercent = 1;
			addEventListener(Event.ENTER_FRAME, onEF);
		}
		
		protected function onEF(event:Event):void
		{
			//每秒溶解1%的像素
			currentPercent -= 0.01;
			
			if( currentPercent > 0 )
			{
				bmd.threshold(bmd, bmd.rect, point, ">=",  (currentPercent * 0xFFFFFF), 0, 0xFFFFFF);
			}
			else
			{
				removeEventListener(Event.ENTER_FRAME, onEF);
			}
		}
	}
}

溶解的關鍵就在於每幀都調用threshold方法,因爲每一幀currentPercent的值都在變小,因此currentPercent * 0xFFFFFF的值也在每幀變小,當原圖像所比較的像素顏色愈來愈暗(接近0)的時候原圖像中原先較亮的顏色會先變透明,以後比較暗的顏色也會漸漸hold不住……有的同窗表示玩了剛纔那個例子後,不像我以前講的,全部像素都溶解成了透明的,而是溶解成了黑色,這是怎麼回事呢?我明明給threshold方法的color參數設置的是徹底透明的值0啊~通過個人研究,發現事實上是我源圖片sourceBMD未開啓透明通道的緣故,只要在構造函數裏面這樣改一下就行了:

public function SimpleDissolve()
{
	stage.scaleMode = StageScaleMode.NO_SCALE;
			
	var bmd:BitmapData = new sourceImg().bitmapData;
	sourceBMD = new BitmapData(bmd.width, bmd.height, true, 0);
	sourceBMD.draw(bmd);
			
	runPostImageLoad();
}

使用先創建一個底色爲全透明的bitmapData對象再draw的方法就能確保sourceBMD可以開啓透明通道了,運行效果我就不上了,列位本身試試就知道了,溶解到最後會看見舞臺的底色(白色)。

好了,如今再改裝一下剛纔的例子,讓咱們用多張圖片來建立一個溶解的圖片切換效果吧,先看效果:

這個效果就是點擊一張圖片後切換到另外一張,並帶有一個溶解的過渡效果。下面來看源碼:

package 
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	
	[SWF(width="400",height="600")]
	public class DissolveSwitch extends Sprite
	{
		[Embed(source="assets/fungus.jpg")]
		private var sourceImg:Class;
		[Embed(source="assets/hydrant.jpg")]
		private var sourceImg2:Class;
		
		//源圖片存儲列表
		private var sourceList:Array;
		
		//擋在前面的用於溶解的圖片
		private var front:Bitmap;
		//躲在後面的真實顯示的圖片
		private var background:Bitmap;
		
		//當前瀏覽的圖片索引
		private var currentImgIndex:int=0;
		private var currentPercent:Number = 1;
		private var point:Point = new Point();

		
		public function DissolveSwitch()
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			
			sourceList = new Array();
			
			//先初始化須要切換的圖片的位圖數據
			generateSourceBMD(sourceImg);
			generateSourceBMD(sourceImg2);
			
			background = new Bitmap( getBMDFromList() );
			addChild(background);
			front = new Bitmap();
			
			stage.addEventListener(MouseEvent.CLICK, onClick);
		}
		
		private function generateSourceBMD(sourceClass:Class):void
		{
			var bmd:BitmapData = (new sourceClass() as Bitmap).bitmapData;
			sourceList.push(bmd);
		}
		
		/** 獲得在位圖數據源數組sourceList中當前瀏覽圖片索引號currentImgIndex所對應的位圖數據 */
		private function getBMDFromList():BitmapData
		{
			//不能使用bitmapData.clone()方法進行位圖數據的拷貝,由於這樣拷貝出的副本不具有透明層
			var copyFrom:BitmapData = sourceList[currentImgIndex] as BitmapData;
			var result:BitmapData = new BitmapData(copyFrom.width, copyFrom.height, true, 0);
			result.draw(copyFrom);
			return result;
		}
		
		protected function onClick(event:Event):void
		{
			//開始播放動畫就不讓你點了先
			stage.removeEventListener(MouseEvent.CLICK, onClick);
			currentPercent = 1;
			
			//複製一份當前顯示圖片並蓋在上面用做溶解
			front.bitmapData = getBMDFromList();
			addChild(front);
			
			//由於有一份副本蓋在上面,因此我原圖片此時能夠在幕後換衣服了
			currentImgIndex++;
			if( currentImgIndex >= sourceList.length )
			{
				currentImgIndex = 0;
			}
			background.bitmapData = getBMDFromList();
			
			addEventListener(Event.ENTER_FRAME, onEF);
		}
		
		protected function onEF(event:Event):void
		{
			//每秒溶解1%的像素
			currentPercent -= 0.01;
			
			if( currentPercent >= 0 )
			{
				front.bitmapData.threshold(front.bitmapData, front.bitmapData.rect, point, ">=",  
					(currentPercent * 0xFFFFFF), 0, 0xFFFFFF);
			}
			else
			{
				removeChild(front);
				removeEventListener(Event.ENTER_FRAME, onEF);
				stage.addEventListener(MouseEvent.CLICK, onClick);
			}
		}
	}
}

上面的代碼我以爲沒有解釋的必要了。雖然溶解效果的代碼咱們知道怎麼寫,可是正要建立一個圖片切換的溶解動畫未必像想象中那樣簡單,要你寫的話你能很順利地寫出來麼?

對於這種溶解,我以爲不夠華麗,並且溶解的順序是因圖片而異的,老是亮色先溶解而後才輪到暗色,那麼有沒有一種隨機化較強且看起來比這個炫的溶解效果呢?哦,是的,這個時候又須要回到咱們的正題——柏林噪聲上來了,由於柏林噪聲容許咱們建立一個隨機化顏色的圖案。

如今咱們須要生成一副相似於雨後的地面的圖片,上面有一灘灘的積水的感受,以下:

要製做這種效果,使用bitmapData.perlinNoise方法是很容易作到的,只須要簡單地設置一下它的參數便可:

perlinNoiseBMD.perlinNoise(50, 50, 2, Math.random(), true, true, 7, true);

回頭看看以前製做溶解效果的代碼:

front.bitmapData.threshold(front.bitmapData, front.bitmapData.rect....)

這句話的意思是以front.bitmapData做爲顏色檢測源對其自身front.bitmapData進行像素溶解,那麼如今咱們只須要把顏色檢測源改爲使用柏林噪聲生成的隨機化圖像就能夠作出一種隨機溶解的感受

public class DissolveSwitch extends Sprite
{
	……
	
	//柏林噪聲隨機圖像生成源
	private var perlinNoiseBMD:BitmapData;
	
	……

	protected function onClick(event:Event):void
	{
		……
		
		//複製一份當前顯示圖片並蓋在上面用做溶解
		front.bitmapData = getBMDFromList();
		addChild(front);
		perlinNoiseBMD = new BitmapData(front.width, front.height, false);
		perlinNoiseBMD.perlinNoise(50, 50, 2, Math.random(), true, true, 7, true);
		
		……
		
		addEventListener(Event.ENTER_FRAME, onEF);
	}
	
	protected function onEF(event:Event):void
	{
		//每秒溶解1%的像素
		currentPercent -= 0.01;
		
		if( currentPercent >= 0 )
		{
			front.bitmapData.threshold(perlinNoiseBMD, perlinNoiseBMD.rect, point, ">=",  
				(currentPercent * 0xFFFFFF), 0, 0xFFFFFF);
		}
		else
		{
			removeChild(front);
			removeEventListener(Event.ENTER_FRAME, onEF);
			stage.addEventListener(MouseEvent.CLICK, onClick);
		}
	}
}

最終效果以下:

看起來正如標題同樣,有一種滲透性溶解的感受對不對?對的話就點點頭,無論你點哪一個頭……

 

平滑型顏色變換及繪圖

當我在找尋有關perlinNoise相關資料的時候看到了一篇老外寫的文章:

http://blog.stroep.nl/2008/11/make-things-less-static-add-some-movement-with-perlin-noise/

在這篇文章中給了我新的啓示,就是利用柏林噪聲生成的隨機顏色做爲一個隨機數(由於顏色值就是一個數字)進行一系列變換效果的實現。有人問,既然要產生隨機數,幹嗎不用Math.random方法呢?咱們注意到,使用Math.random方法的話,每次產生的數值之間的相隔距離沒法肯定,有時會產生激烈的波動。好比我在0-100間使用Math.random隨機出兩個數字,這兩個數字可能出現一個0,一個99,這樣它們的相隔距離比較遠,相差了98個數,這樣產生的結果由於波動可能過大,就沒法制做出一些平滑的隨機效果。那麼咱們回過頭來看一下使用柏林噪聲產生的一份顏色圖:

上面這份顏色圖是設置perlinNoise的stitch和fractalNoise參數爲true(設置stitch爲true可使圖像的像素間顏色差距不會太大,即對柏林雜點圖像進行平滑處理;設置fractalNoise爲true則生成像素間顏色起伏不會太大的「雲彩」,若設爲false則會生成一團一團的「煙霧」)且開啓3個顏色通道的結果。咱們注意到,使用perlinNoise方法產生的圖像中每一個像素間顏色差距不會太大,這就是說咱們可使用這些顏色值來產生一些較爲平滑,波動不大的隨機數。

接下來讓咱們看例子吧,這個例子與剛纔貼出來的那個老外的文章裏給的例子相似,每幀都重繪一個矩形,它的顏色和寬度不停地在改變,可是注意觀察能夠發現矩形的寬度變化波動不會太大,較爲平滑:

下面給出實現它的源代碼:

package
{
	import flash.display.BitmapData;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	
	[SWF(width="300",height="200", frameRate="40")]
	public class RandomShader extends Sprite
	{
		private var xPos:int;

		private var noise:BitmapData;

		private var shape:Shape;
		
		private var xSpeed:Number;
		private var ySpeed:Number;
		
		public function RandomShader()
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			
			xPos = 0; 
			noise = new BitmapData( 900, 1 ); 
			
			//產生柏林噪聲雜點圖像
			noise.perlinNoise ( 50, 3, 3, Math.random()*100, true, true ); 
			
			shape = new Shape();
			this.addChild ( shape );   
			
			this.addEventListener ( Event.ENTER_FRAME, update ); 
			
		}
		
		private function update( e:Event ):void { 
			
			//每幀讓xPos值加1,而後取位於(xPos,0)位置的顏色值
			xPos = ( xPos > noise.width ) ? 0 : xPos + 1;   
			var color:uint = noise.getPixel( xPos,0 );      
			
			//取出三色值
			var red:uint = color >> 16;
			var green:uint = color >> 8 & 0xFF;
			var blue:uint = color & 0xFF;
				
			//使用紅色值做爲矩形的寬度
			shape.graphics.clear(); 
			shape.graphics.beginFill( color );
			shape.graphics.drawRect( 0, 0, red, 200 );
			
		}
	}
}

 

漫遊運動

正是因爲使用柏林噪聲產生的隨機數較爲平滑,咱們就能夠利用這一輩子成的隨機數做爲物體運動的速度,以此來達到一種漫遊的運動效果,爲了讓運動速度有正有負,咱們須要在取出一個顏色值以後讓它減去0x80,即0xFF的一半。

package
{
	import flash.display.BitmapData;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	
	[SWF(width="640",height="480", frameRate="40")]
	public class PolinNoiseWander extends Sprite
	{
		private var xPos:int;
		
		private var noise:BitmapData;
		
		private var shape:Shape;
		
		private var xSpeed:Number;
		private var ySpeed:Number;
		
		public function PolinNoiseWander()
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			
			xPos = 0; 
			noise = new BitmapData( 900, 1 ); 
			
			//產生柏林噪聲雜點圖像
			noise.perlinNoise ( 50, 3, 3, Math.random()*100, true, true ); 
			
			shape = new Shape();
			this.addChild ( shape );   
			shape.graphics.beginFill(0xff0000);
			shape.graphics.drawCircle(0,0,10);
			shape.x = stage.stageWidth / 2;
			shape.y = stage.stageHeight / 2;
			
			this.addEventListener ( Event.ENTER_FRAME, update ); 
			
		}
		
		private function update( e:Event ):void { 
			
			//每幀讓xPos值加1,而後取位於(xPos,0)位置的顏色值
			xPos = ( xPos > noise.width ) ? 0 : xPos + 1;   
			var color:uint = noise.getPixel( xPos,0 );      
			
			//取出三色值
			var red:uint = color >> 16;
			var green:uint = color >> 8 & 0xFF;
			var blue:uint = color & 0xFF;
			
			//利用柏林噪聲生成的雜點圖像中某點顏色值來做爲運動速度
			xSpeed = (red - 0x80)/10;
			ySpeed = (green - 0x80)/10;
			
			shape.x += xSpeed;
			shape.y += ySpeed;
			
			//走到邊緣則從另外一側邊緣出現
			if( shape.x > stage.stageWidth )
			{
				shape.x = shape.x - stage.stageWidth;
			}
			else if( shape.x < 0 )
			{
				shape.x = stage.stageWidth - shape.x;
			}
			if( shape.y > stage.stageHeight )
			{
				shape.y = shape.y - stage.stageHeight;
			}
			else if( shape.y < 0 )
			{
				shape.y = stage.stageHeight - shape.y;
			}
			
		}
	}
}

下面是運行效果(點擊圖片觀看

這比起《動畫高級教程》(長頸鹿書)上面介紹的使用Math.random方法實現的「漫遊行爲」比起來更加具備「生命力」,實現起來也更加簡單,且節省運算量哦親~

相似地,若是使用上面這個例子中某一個方向上的速度來做爲上下波動的幅度繪圖,能夠輕易地模擬出心電圖或是閃電效果:

代碼就不給出了,你們本身思考一下怎麼寫吧~

相關文章
相關標籤/搜索