【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方法实现的“漫游行为”比起来更加具备“生命力”,实现起来也更加简单,且节省运算量哦亲~

相似地,若是使用上面这个例子中某一个方向上的速度来做为上下波动的幅度绘图,能够轻易地模拟出心电图或是闪电效果:

代码就不给出了,你们本身思考一下怎么写吧~

相关文章
相关标签/搜索