返回“Flash基础理论课 - 目录”
滤镜(Filter)
滤镜是一些位图的效果,可以应用于任何显示对象,
Flash基础理论课 第四章 渲染技术Ⅲ
。在 Flash IDE 中可以通过滤镜面板或使用时间轴的 ActionScript. 来使用滤镜,由于这本书是关于 ActionScript. 的,所以只能简单地讨论一下应用滤镜的方法。在 AS 3 中包括以下几种滤镜:■ Drop shadow(投影滤镜)
■ Blur(模糊滤镜)
■ Glow(发光滤镜)
■ Bevel(斜角滤镜)
■ Gradient bevel(渐变斜角滤镜)
■ Gradient glow(渐变发光滤镜)
■ Color matrix(颜色矩阵滤镜)
■ Convolution(卷积滤镜)
Displacement map(置换图滤镜)
虽然不能一一介绍每种滤镜的使用细节,但大家可以通过帮助文档来学习。在书中还会有很多滤镜使用的例子,所以在这里只介绍一下滤镜使用的总体方法和两个具体实例。
创建滤镜
通过使用 new 关键字及滤镜名来创建滤镜,并给出所需的参数。例如,创建一个 blur filter(模糊滤镜),最简单的一种滤镜,写法入下:
var blur:BlurFilter = new BlurFilter(5, 5, 3);
参数分别为 blurX,blurY,quality。这个例子会将对象在x和y轴上模糊 5 个像素,模糊的品质为中等。
另一点需要知道的是滤镜在其名为 flash.filters 的包中。所以要在文件的开始处将它们导入进来:
import flash.filters.BlurFilter;
如果希望导入包中所有的滤镜,可以使用简写:
import flash.filters.*;
现在,我们可以直接创建任何类型的滤镜了,但是一般来说,除非要使用这个包中的大部分滤镜,否则最好避免使用通配符(*),而是明确地导入所需要的类。这样做只是为了能够清楚,哪些是真正想要导入的而哪些不是。好了,现在已经创建了一个模糊滤镜,但怎么才能使它去模糊一个对象呢?
任何一个显示对象都有一个名为 filters 的属性,这是一个包括了所有滤镜的数组,因为如果一个对象要应用多个滤镜,那么只需要再将模糊滤镜放到数组中即可。乐观地看,应用滤镜应该可以像使用基本数组操作那样简单,push,就像这样 mySprite.filters.push(blur);,但是很遗憾,没有这么简单。在整个数组赋值为 filters 之前,Flash 不关心 filters 数组的变化。
如果已知对象没有应用任何的滤镜,或想要重写它们,只需要新建一个数组,将我们的滤镜粘在上面,再将这个新数组赋给 filters 属性就可以了。先来试一下,下面一个文档类 Filters.as,创建了一个 sprite 影片并且在里面绘制了一个黄色的正方形,然后,创建一个滤镜,加入数组中,最后将数组赋给 sprite 的 filters 属性:
package {
import flash.display.Sprite;
import flash.filters.BlurFilter;
public class Filters extends Sprite {
public function Filters() {
init();
}
private function init():void {
var sprite:Sprite = new Sprite();
sprite.graphics.lineStyle(2);
sprite.graphics.beginFill(0xffff00);
sprite.graphics.drawRect(100, 100, 100, 100);
sprite.graphics.endFill();
addChild(sprite);
var blur:BlurFilter = new BlurFilter(5, 5, 3);
var filters:Array = new Array();
filters.push(blur);
sprite.filters = filters;
}
}
}
瞧!出现了一个模糊的黄色方块儿。重要的部分用黑体着重,我们可以简写一点:
var blur:BlurFilter = new BlurFilter(5, 5, 3);
var filters:Array = [blur];
sprite.filters = filters;
或再短一点:
sprite.filters = [new BlurFilter(5, 5, 3)];
在创建数组的同时,将滤镜放进去,并应用 filters 属性,这样一来,Flash 会很高兴。
但是如果已经有了滤镜并希望继续使用,这时,但又不确定是否有滤镜存在,那该怎么办呢?在 Flash 8 中,这是件很麻烦的事,因为一个显示对象的 filters 属性如果没有应用滤镜,那么它将是未定义(undefined)的。但在 AS 3 中, filters 数组总是保持为一个空数组,只需要给数组赋值,将滤镜 push 进去,并将其赋给对象的 filters 属性即可,方法如下:
var filters:Array = sprite.filters;
filters.push(new BlurFilter(5, 5, 3));
sprite.filters = filters;
如果使用这种方法,那么无论是否有滤镜存在都没有问题,滤镜只是被加入到数组列表中而已。因为 filters 属性是一套成熟的数组,所以可以使用不同的数组操作方法。比如,使用 concat 方法:
sprite.filters = sprite.filters.concat(new BlurFilter(5, 5, 3));
我不认为这是个“正确”的做法,大家只要知道将一个包涵有滤镜的数组赋给 filters 属性就足够了。
动态滤镜
现在我们已经基本上知道了如何在 ActionScript. 中使用滤镜了。接下来,用已经学过的知识,制作一个动态滤镜。这个效果,使用文档类 AnimatedFilters.as:
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.filters.DropShadowFilter;
public class AnimatedFilters extends Sprite {
private var filter:DropShadowFilter;
private var sprite:Sprite;
public function AnimatedFilters() {
init();
}
private function init():void {
sprite = new Sprite();
sprite.graphics.lineStyle(2);
sprite.graphics.beginFill(0xffff00);
sprite.graphics.drawRect(-50, -50, 100, 100);
sprite.graphics.endFill();
sprite.x = 200;
sprite.y = 200;
addChild(sprite);
filter = new DropShadowFilter(0, 0, 0, 1, 20, 20, .3);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
var dx:Number = mouseX - sprite.x;
var dy:Number = mouseY - sprite.y;
filter.distance = -Math.sqrt(dx * dx + dy * dy) / 10;
filter.angle = Math.atan2(dy, dx) * 180 / Math.PI;
sprite.filters = [filter];
}
}
}
首先在 sprite 中画一个正方形,正方形在 sprite 的居中位置,然后将 sprite 移动到舞台中间,用一些默认属性创建投影滤镜(DropShadowFilter)。
添加一个 enterFrame. 事件的侦听器及处理函数:onEnterFrame. 方法,用于计算角度(angle)及使用三角函数计算 sprite 影片与鼠标的距离(distance)。使用 angle 和distance 设置投影滤镜的 angle 和distance 属性,最后将这个滤镜再应用到 sprite 上。请注意,我们不需要每次都创建一个新的滤镜,可以继续使用同一个滤镜,只需要改变它的属性即可。然而,只是改变这些属性也不能更新 sprite 影片的显示。因此,还需要再将变化过的滤镜效果赋值给 filters 属性。
位图(Bitmaps)
与滤镜相同,可以用整本书来介绍 Bitmap 和BitmapData 类,看起来也不错,但是这并不是本书的目的。我们将通过一些简单的例子,用来指出 AS 2 与 AS 3 中位图处理的变化。
在 AS 2 中,通过调用 BitmapData()函数,新建一个 BitmapData 对象使用如下参数:
new BitmapData (width:Number,
height:Number,
transparent:Boolean,
fillColor:Number)
你也许猜到了, BitmapData 类同样也是嵌入在一个包中,完整的使用名称如下 flash.display.BitmapData。所以需要导入包,对于 width 和height 参数则非常显而易见, transparent 参数表示创建的图像是否包涵一个 alpha 通道,选择 true 或 false ,fillColor 是创建图像的初始颜色,如果 transparent 为 true 的话,那么位图就用 32 位色表示,0xAARRGGBB,如果为 false 的话,就可以使用 24 位安全色表示。
在创建 BitmapData 对象时,也许很想能看到它的样子。在 AS 2 中,使用 attachBitmap 命令在影片剪辑中添加一个位图。大家也许会想,现在是否可以使用 addChild 在显示对象中添加一个位图,但事实上并没有这么简单。问题在于 addChild 只对继承自 DisplayObject 类的对象起作用,如 Sprite 影片,影片剪辑和文本框。然而,如果我们研究一下类的结构,就会发现 BitmapData ,没有继承自 DisplayObject,所有不能直接添加对象。这就是为什么要有 Bitmap 类的原因, Bitmap 类几乎始终都有一个函数作为 BitmapData 实例的容器,可以这样创建:
var myBitmapData:BitmapData = new BitmapData(100, 100, false, 0xff0000);
var myBitmap:Bitmap = new Bitmap(myBitmapData);
现在就可以将对象加入到显示列表了:
addChild(myBitmap);
使其可见后,Bitmap 实例也可以改变位置,进行缩放,增加滤镜等等。
测试这个例子,只需要在第二章给出的类框架的 init 方法加入这三行就可以了,不要忘记导入 flash.display.Bitmap 和flash.display.BitmapData,运行后就会看到一个红色的正方形。乍看上去,与使用绘图 API 所画的图形没什么不同,但是要知道这并不是矢量图绘制法:填充一个红色的正方形。这是张位图图像,在位图中每一个像素都要分别指定而且是可变的。事实上,每一个像素值都可以使用 getPixel,getPixel32 和setPixel,setPixel32 进行读取和设置。两个版本的不同之处在于 getPixel 和setPixel 使用24位色彩值忽略了 alpha 通道,而 “32”版的则使用32位色彩值其中包括了透明度信息,
电脑资料
《Flash基础理论课 第四章 渲染技术Ⅲ》(https://www.unjs.com)。让我们来做个例子,制作一个简单的喷漆工具,就像所有位图喷漆程序一样。这里是文档类,SprayPaint.as:
package {
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.filters.BlurFilter;
public class SprayPaint extends Sprite {
private var canvas:BitmapData;
private var color:uint;
private var size:Number = 50;
private var density:Number = 50;
public function SprayPaint() {
init();
}
private function init():void {
canvas = new BitmapData(stage.stageWidth,
stage.stageHeight,
true, 0x00000000);
var bmp:Bitmap = new Bitmap(canvas);
addChild(bmp);
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
private function onMouseDown(event:MouseEvent):void {
color = Math.random() * 0xffffff + 0xff000000;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onMouseUp(event:MouseEvent):void {
removeEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
for (var i:int = 0; i < density; i++) {
var angle:Number = Math.random() * Math.PI * 2;
var radius:Number = Math.random() * size;
var xpos:Number = mouseX + Math.cos(angle) * radius;
var ypos:Number = mouseY + Math.sin(angle) * radius;
canvas.setPixel32(xpos, ypos, color);
}
}
}
}
这也许是目前为止最复杂的代码,但除了 BitmapData 的内容外,其它的知识前面都讲过,只不过又使用了一遍而已。一步步来看,首先,创建了一些类变量,包括 canvas 变量,用于存放 BitmapData 的实例。创建的实例尺寸等于舞台的尺寸,并使用透明的背景色。然后使用 canvas 创建一个位图,并加入到显示列表。
鼠标事件处理函数中选择了一个随机的颜色,并且带有添加和删除 enterFrame. 事件处理函数的功能。我们来回忆一下三角学,首先,从 0 到 Math.PI * 2 中计算出一个随机的角度,不要忘记使用弧度制表示,相当于随机的360度。然后,计算出一个随机的半径后,再使用三角函数将半径和角度转换为 x,y 值。最后使用 setPixel32 以鼠标位置加上随机的 x,y 值的像素点设置为喷漆色,每一次开始喷漆时随机决定颜色。在这个例子中有一个 for 循环,每一帧都会进行循环,每次循环多少次由 density 的值决定。 color 的值为24位的色彩值,然后加上 0xFF000000,为的是设置 alpha 通道为完全不透明,如果没有加上这个值,那么所有的颜色就都为透明的。如果用 0xFFFFFFFF 乘以 Math.random(),那么颜色的透明度是随机的,也许是你想要的,但不是我想要的。通过改变 density 和size 的值再测试一下,看看会有些什么不同的效果。大家也许已经想到如何让用户来控制改变这些参数了。
刚刚看到这个程序时,你也许会想,“真是小题大作,完全可以用绘图 API 或通过加载小影片剪辑并改变颜色来实现”。是的,完成可以这么做,但是如果使用绘图 API 绘出成千上万的独立图像后,会发习画得越多,速度越慢。画过几百个图形后,慢下来的速度会变得非常明显,这个程序也就费掉了,使用加载影片剪辑的方式也是如此。但是,使用位图就完全不同了,我们可以使用这个程序喷上一天,都不影响程序的速度或效率。
如果想看到更酷的效果,就把下面一行代码加在位图对象 bmp 的后面:
bmp.filters = [new BlurFilter(2, 2, 3)];
在位图中使用模糊滤镜比在矢量图中使用效果更加明显。当然,设置像素是 BitmapData 对象能做的最简单的操作之一。除了获取和设置像素,BitmapData 对象还有其它二十多种方法,这些方法可用来复制像素,设置阈值,分解,合并,滚动,等等。我个人最喜欢的一个是 perlinNoise 方法,该函数允许我们创建一个随机的有组织的图案。对于制造烟,云和水波纹效果都非常有用。有兴趣的话大家可以试验一下。
读取和嵌入资源
最后一个重点话题是获取外部资源的概念,如在影片中加载位图或外部 SWF 文件。有两种方法,一种是在动画播放时将资源读入,这就是我们所熟知的读取(loading)。另一种方法是在 SWF 编译时嵌入(embed)资源。
读取资源
创建一个 Loader 对象来读取一个资源,这是flash.display.Loader 类的一个实例。 loader 是个显示对象,意味着可以使用 addChild() 方法将它加入到显示列表中,就像 sprite 和bitmap 一样。然后告诉这个 loader 去读取一个外部 SWF 或外部位图,如 JPEG,PNG,等等。
在 AS 2 中,当处理外部文件路径或 URL 时,只需要使用一个简单的字符串表示路径。 在 AS 3 中,则需要创建一个 flash.net.URLLoader 实例,传入表示路径的字符串,并且还需要一个额外的步骤,虽然有些烦人,但是我们还是要习惯这种用法。
这里是一个在运行时读取外部资源的例子(文档类 LoadAsset.as):
package {
import flash.display.Sprite;
import flash.display.Loader;
import flash.net.URLRequest;
public class LoadAsset extends Sprite {
public function LoadAsset() {
init();
}
private function init():void {
var loader:Loader = new Loader();
addChild(loader);
loader.load(new URLRequest("picture.jpg"));
}
}
}
嵌入资源
虽然在有些情况下,在运行时读取资源很合适,但是在有些情况下有一些外部图形只想加载到 SWF 自里面。这时,如果使用 Flash IDE,可以简单地导入这个对象到库中并设置为“为 ActionScript. 导出”。但在使用 Flex Builder 2 或 Flex 2 SDK 命令编译器时,没有库,那么如何在 SWF 中加载外部资源呢?
答案是使用[Embed]元数据(metadata)标签嵌入资源,元数据标签是指加到 ActionScript. 文件中的非正式 ActionScript. 语句。另外,它们指示编译器在编译过程中去做某种事情,[Embed]标签告诉编译器在最终的 SWF 文件中加载一个特殊的外部资源,资源可以是位图或外部 SWF 文件。告诉编译器要嵌入的资源所在的 source 路径的属性,如下:
[Embed(source="picture.jpg")]
在元数据语句的后面,直接声明一个 Class 类型的变量,如下:
[Embed(source="picture.jpg")]
private var Image:Class;
现在可以使用这个变量创建一个新的资源实例,如下:
var img:Bitmap = new Image();
注意创建的这个对象是 Bitmap 类型的。如果嵌入一个外部 SWF 文件,创建的这个对象应该是 Sprite 类型的,如下:
[Embed(source="animation.swf")]
private var Anim:Class;
var anim:Sprite = new Anim();
这里是一个在 SWF 中嵌入外部 JPEG 的例子:
package {
import flash.display.Sprite;
import flash.display.Bitmap;
public class EmbedAsset extends Sprite {
[Embed(source="picture.jpg")];
private var Image:Class;
public function EmbedAsset() {
init();
}
private function init():void {
var img:Bitmap = new Image();
addChild(img);
}
}
}
如果我们使用 Flash IDE ,只要将对象导入到库中并“为 ActionScript. 导出”给出一个类名就可以了。不需要使用 [Embed] 元数据标签及类变量,事实上,Flash IDE 编译器甚至不支持 [Embed] 元数据标签。这里只作一个简单的介绍,因为在本书后面的内容中不会用到这个技术,但是很显然这是个非常有用的方法。
本章重要公式
在本章中我们又收集了很多有价值的工具,大多都与颜色有关。
转换为十进制:
trace(hexValue);
十进制转换为十六进制:
trace(decimalValue.toString(16));
颜色合成:
color24 = red << 16 | green << 8 | blue;
color32 = alpha << 24 | red << 16 | green << 8 | blue;
颜色提取:
red = color24 >> 16;
green = color24 >> 8 & 0xFF;
blue = color24 & 0xFF;
alpha = color32 >> 24;
red = color32 >> 16 & 0xFF;
green = color32 >> 8 & 0xFF;
blue = color232 & 0xFF;
过控制点的曲线:
// xt, yt is the point you want to draw through
// x0, y0 and x2, y2 are the end points of the curve
x1 = xt * 2 – (x0 + x2) / 2;
y1 = yt * 2 – (y0 + y2) / 2;
moveTo(x0, y0);
curveTo(x1, y1, x2, y2);