如果把Math.random方法作为一个生成随机数字的办法,那么bitmapData.perlinNoise就是一个生成随机颜色的办法。在这一部分的对于噪声的应用介绍文章中我们一起来看看使用柏林噪声的随机化像素功能能为我们完成什么样的随机化效果。
本文章源码下载:www.iamsevent.com/zb_users/upload/AS3Coder4/AS3Coder4_2.rar
渗透型溶解效果
想必列位在看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()
方法,您可以隔离和替换图像中的颜色范围,并对图像像素执行其它逻辑操作。
threshold()
方法的测试逻辑如下所示:
如果
((pixelValue & mask) operation (threshold & mask))
,则将像素设置为color
;否则,如果
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的颜色事实上是由亮度分别为12、34、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方法实现的“漫游行为”比起来更加具有“生命力”,实现起来也更加简单,且节省运算量哦亲~
类似地,如果使用上面这个例子中某一个方向上的速度来作为上下波动的幅度绘图,可以轻易地模拟出心电图或是闪电效果:
代码就不给出了,大家自己思考一下怎么写吧~