位图示例:带动画效果的旋转的月亮Flash Player 9 和更高版本,Adobe AIR 1.0 和更高版本 旋转的月球动画示例说明了位图对象和位图图像数据(BitmapData 对象)的使用方法。该示例使用月球表面的平面图像作为原始图像数据来创建一个旋转的月球动画。将对以下方法进行说明:
若要获取此范例的应用程序文件,请参阅 www.adobe.com/go/learn_programmingAS3samples_flash_cn。可以在 Samples/SpinningMoon 文件夹中找到“旋转的月球动画”的应用程序文件。该应用程序包含以下文件:
将外部图像作为位图数据加载此范例执行的第一项主要任务是加载外部图像文件,即月球表面照片。加载操作由 MoonSphere 类中的两个方法处理:MoonSphere() 构造函数(启动加载过程)和 imageLoadComplete() 方法(完成外部图像加载后调用该方法)。 加载外部图像与加载外部 SWF 类似,两者都使用 flash.display.Loader 类的实例执行加载操作。启动图像加载的 MoonSphere() 方法中的实际代码如下: var imageLoader:Loader = new Loader(); imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageLoadComplete); imageLoader.load(new URLRequest("moonMap.png")); 第一行声明名为 imageLoader 的 Loader 实例。第三行通过调用 Loader 对象的 load() 方法并传递一个 URLRequest 实例(表示要加载的图像的 URL)来实际启动加载过程。第二行设置在完成图像加载时将触发的事件侦听器。请注意:不是对 Loader 实例本身调用 addEventListener() 方法;而是对 Loader 对象的 contentLoaderInfo 属性调用该方法。Loader 实例本身不会调度与所加载内容相关的事件。然而,它的 contentLoaderInfo 属性包含对 LoaderInfo 对象的引用,该对象与加载到 Loader 对象的内容(本例中为外部图像)相关联。该 LoaderInfo 对象提供与外部内容加载进度及加载完成有关的几个事件,其中包括 complete 事件 (Event.COMPLETE),该事件将在完成图像加载时触发对 imageLoadComplete() 方法的调用。 启动外部图像加载是加载过程中的重要部分,而了解加载完成时要执行什么操作也同等重要。如以上代码所示,完成加载图像后将调用 imageLoadComplete() 函数。该函数对加载的图像数据执行一些操作,稍后将进行说明。然而,若要使用图像数据,还需要访问该数据。当使用某 Loader 对象来加载外部图像时,加载的图像将变为一个 Bitmap 实例,并附加为该 Loader 对象的一个子显示对象。在本例中,Loader 实例可作为事件对象的一部分用于事件侦听器方法,此事件对象将作为参数传递给该方法。imageLoadComplete() 方法的前几行如下: private function imageLoadComplete(event:Event):void { textureMap = event.target.content.bitmapData; ... } 请注意,事件对象参数名为 event,它是 Event 类的一个实例。Event 类的每个实例都具有一个 target 属性,该属性将引用触发事件的对象(本例中为 LoaderInfo 实例,如前所述,将对该实例调用 addEventListener() 方法。)而 LoaderInfo 对象又具有一个 content 属性,加载过程完成后,该属性将包含 Bitmap 实例,其中具有加载的位图图像。如果要在屏幕上直接显示该图像,则可以将此 Bitmap 实例 (event.target.content) 附加到一个显示对象容器。(也可以将 Loader 对象附加到一个显示对象容器。)但是,在该示例中,加载的内容将用作原始图像数据的源而不是显示在屏幕上。因此,imageLoadComplete() 方法的第一行读取加载的 Bitmap 实例的 bitmapData 属性 (event.target.content.bitmapData),并将其存储在名为 textureMap 的实例变量中,该变量用作创建旋转的月球动画的图像数据源。将稍后对此进行说明。 通过复制像素创建动画动画的基本定义是通过随时间变化改变图像而产生的运动或变化的视觉效果。此示例的目标是创建月球绕其垂直轴旋转的视觉效果。然而,对于动画而言,您可以忽略该示例中球形扭曲方面的问题。假设已加载并用作月球图像数据源的实际图像如下: 如您所见,该图像并不是一个或几个球体;它是月球表面的一张矩形照片。因为该照片刚好是在月球赤道上拍摄的,所以图像中靠近图像顶部和底部的部分发生拉伸和扭曲。若要消除图像的扭曲使其具有球形外观,我们将使用置换图滤镜(在后面进行介绍)。但是,因为该源图像是矩形,所以若要产生旋转球体的视觉效果,代码只要能完成水平滑动月球表面照片的操作即可。 请注意,该图像实际上由月球表面照片的两个副本彼此相接而成。该图像是要从中重复复制图像数据来创建动画外观的源图像。通过两个图像副本彼此相接,会更容易产生连续、不间断的滚动效果。让我们逐步浏览动画生成的过程来看看这是如何实现的。 该过程实际上涉及两个单独的 ActionScript 对象。首先,有加载的源图像,在代码中由名为 textureMap 的 BitmapData 实例表示。如前所述,外部图像加载后,将用图像数据来填充 textureMap,使用的代码如下: textureMap = event.target.content.bitmapData; textureMap 的内容是矩形的月球图像。另外,为了产生旋转动画,该代码使用名为 sphere 的 Bitmap 实例,该实例是在屏幕上显示月球图像的实际显示对象。与 textureMap 一样,sphere 对象也是在 imageLoadComplete() 方法中创建并使用其初始图像数据填充,使用的代码如下: sphere = new Bitmap(); sphere.bitmapData = new BitmapData(textureMap.width / 2, textureMap.height); sphere.bitmapData.copyPixels(textureMap, new Rectangle(0, 0, sphere.width, sphere.height), new Point(0, 0)); 如代码所示,sphere 被实例化了。其 bitmapData 属性(通过 sphere 显示的原始图像数据)具有与 textureMap 相同的高度和一半的宽度。换句话说,sphere 的内容将是一幅月球照片的大小(因为 textureMap 图像包含并排的两幅月球照片)。接下来,用图像数据填充 bitmapData 属性,填充时使用的是 copyPixels() 方法。copyPixels() 方法调用中的参数指明以下几点:
从视觉表示形式上看,该代码将复制下图中用轮廓线标出的、textureMap 的像素,并将其粘贴到 sphere 上。换句话说,sphere 的 BitmapData 内容是这里加亮的 textureMap 部分: 然而请记住,这只是 sphere 的初始状态 — 复制到 sphere 上的第一项图像内容。 加载源图像并创建 sphere 之后,由 imageLoadComplete() 方法执行的最终任务是设置动画。动画由名为 rotationTimer 的 Timer 实例驱动,该实例由以下代码创建并启动: var rotationTimer:Timer = new Timer(15); rotationTimer.addEventListener(TimerEvent.TIMER, rotateMoon); rotationTimer.start(); 代码首先创建名为 rotationTimer 的 Timer 实例;传递给 Timer() 构造函数的参数指明 rotationTimer 应每 15 毫秒触发一次其 timer 事件。接下来将调用 addEventListener() 方法,以指定在发生 timer 事件 (TimerEvent.TIMER) 时调用 rotateMoon() 方法。最后,计时器实际上是通过调用其 start() 方法启动的。 根据 rotationTimer 的定义方式,约每 15 毫秒 Flash Player 调用一次 MoonSphere 类中的 rotateMoon() 方法(用于产生月球动画)。rotateMoon() 方法的源代码如下: private function rotateMoon(event:TimerEvent):void { sourceX += 1; if (sourceX > textureMap.width / 2) { sourceX = 0; } sphere.Data.copyPixels(textureMap, new Rectangle(sourceX, 0, sphere.width, sphere.height), new Point(0, 0)); event.updateAfterEvent(); } 该代码实现以下三方面的操作:
请记住,此代码每 15 毫秒重复调用一次。由于源矩形的位置是连续移动的,并且像素被复制到 sphere 上,因此在屏幕上显示为由 sphere 表示的月球照片图像发生连续滑动。换句话说,月球显示为连续旋转。 创建球形外观当然,月球是一个球体而不是一个矩形。由于要形成连续动画,因此该示例需要拍摄矩形的月球表面照片,并将其转换成球体。这涉及两个单独的步骤:一个用于隐藏月球表面照片中除圆形区域之外的所有内容的遮罩,以及一个用于扭曲月球照片外观使其具有三维外观的置换图滤镜。 首先,圆形遮罩用于隐藏 MoonSphere 对象中除通过滤镜创建的球体之外的所有内容。以下代码创建一个作为 Shape 实例的遮罩,并将其应用为 MoonSphere 实例的遮罩: moonMask = new Shape(); moonMask.graphics.beginFill(0); moonMask.graphics.drawCircle(0, 0, radius); this.addChild(moonMask); this.mask = moonMask; 请注意,由于 MoonSphere 是一个显示对象(它基于 Sprite 类),因此可以使用该显示对象继承的 mask 属性将遮罩直接应用于 MoonSphere 实例。 仅使用圆形遮罩隐藏照片的某些部分不足以创建逼真的旋转球体效果。由于月球表面照片拍摄方式的限制,导致照片的尺寸不成比例;与赤道上的部分相比,图像上越靠近图像顶部或底部的部分扭曲和拉伸得越严重。为了对月球照片的外观进行变形以使其具有三维效果,我们将使用置换图滤镜。 置换图滤镜是一种用于扭曲图像的滤镜。在本例中,将通过水平挤压图像的顶部和底部而保持中部不变来“扭曲”月球照片,从而使其看起来更加逼真。假设对照片的正方形部分执行滤镜操作,挤压顶部和底部而不挤压中部将使正方形变为圆形。为该扭曲图像添加动画效果所产生的另一效果是:与靠近顶部和底部的区域相比,图像的中部看起来所移动的实际像素距离更大,这将产生圆实际上是一个三维对象(球体)的视觉效果。 以下代码用于创建名为 displaceFilter 的置换图滤镜: var displaceFilter:DisplacementMapFilter; displaceFilter = new DisplacementMapFilter(fisheyeLens, new Point(radius, 0), BitmapDataChannel.RED, BitmapDataChannel.GREEN, radius, 0); 第一个参数 fisheyeLens 称为置换图图像;在本例中,它是以编程方式创建的 BitmapData 对象。将在通过设置像素值创建位图图像中介绍创建该图像的说明。其他参数说明过滤后的图像中应该应用滤镜的位置、使用哪些颜色通道来控制置换效果以及将在何种范围内对置换产生影响。一旦创建置换图滤镜,它将应用于仍位于 imageLoadComplete() 方法中的 sphere: sphere.filters = [displaceFilter]; 应用了遮罩和置换图滤镜的最终图像如下所示: 每个旋转月球动画循环之后,sphere 的 BitmapData 内容将由新的源图像数据快照覆盖。但是,无需每次都重新应用滤镜。这是因为滤镜应用到了 Bitmap 实例(显示对象)而不是位图数据(原始像素信息)。请记住,Bitmap 实例不是实际的位图数据;它是在屏幕上显示位图数据的显示对象。举例来说,Bitmap 实例就像用于在屏幕上显示照片幻灯片的幻灯片放映机,而 BitmapData 对象就像可以通过幻灯片放映机显示的实际照片幻灯片。滤镜可以直接应用于 BitmapData 对象,这与直接在照片幻灯片上绘图以更改图像相似。滤镜也可以应用于任何显示对象(包括 Bitmap 实例);这与在幻灯片放映机的镜头前面放一个滤镜以使屏幕上显示的输出变形(丝毫不会更改原始幻灯片)相似。因为可通过 Bitmap 实例的 bitmapData 属性来访问原始位图数据,所以滤镜可能已直接应用于原始位图数据。然而,在本例中,将滤镜应用于 Bitmap 显示对象比应用于位图数据更有意义。 有关在 ActionScript 中使用置换图滤镜的详细信息,请参阅过滤显示对象。 通过设置像素值创建位图图像置换图滤镜的一个重要方面是它实际上涉及两个图像。一个图像为源图像,即由滤镜实际更改的图像。在该示例中,源图像是名为 sphere 的 Bitmap 实例。滤镜所用的另一个图像称为映射图像。映射图像不实际显示在屏幕上。相反,它的每个像素的颜色都用作置换函数的输入 — 置换图图像中位于特定 x、y 坐标的像素的颜色决定应用于源图像中位于该 x、y 坐标的像素的置换量(物理位移)。 因此,为了使用置换图滤镜创建球体效果,该范例需要合适的置换图图像 — 一个包含灰色背景和用单一颜色(红色)从暗到亮水平渐变填充的圆的图像,如下图所示: 因为该示例中仅使用一个映射图像和滤镜,所以在 imageLoadComplete() 方法中(换句话说,外部图像完成加载时)只创建一次映射图像。通过调用 MoonSphere 类的 createFisheyeMap() 方法来创建名为 fisheyeLens 的置换图图像: var fisheyeLens:BitmapData = createFisheyeMap(radius); 在 createFisheyeMap() 方法中,在使用 BitmapData 类的 setPixel() 方法绘制置换图图像时,实际上每次只绘制一个像素。下面列出了 createFisheyeMap() 方法的完整代码,并跟有操作方式的逐步说明: private function createFisheyeMap(radius:int):BitmapData { var diameter:int = 2 * radius; var result:BitmapData = new BitmapData(diameter, diameter, false, 0x808080); // Loop through the pixels in the image one by one for (var i:int = 0; i < diameter; i++) { for (var j:int = 0; j < diameter; j++) { // Calculate the x and y distances of this pixel from // the center of the circle (as a percentage of the radius). var pctX:Number = (i - radius) / radius; var pctY:Number = (j - radius) / radius; // Calculate the linear distance of this pixel from // the center of the circle (as a percentage of the radius). var pctDistance:Number = Math.sqrt(pctX * pctX + pctY * pctY); // If the current pixel is inside the circle, // set its color. if (pctDistance < 1) { // Calculate the appropriate color depending on the // distance of this pixel from the center of the circle. var red:int; var green:int; var blue:int; var rgb:uint; red = 128 * (1 + 0.75 * pctX * pctX * pctX / (1 - pctY * pctY)); green = 0; blue = 0; rgb = (red << 16 | green << 8 | blue); // Set the pixel to the calculated color. result.setPixel(i, j, rgb); } } } return result; } 首先,调用该方法时,将接收参数 radius,指示要创建的圆形图像的半径。接下来,该代码将创建 BitmapData 对象(将在该对象上绘制圆)。该对象名为 result,最终将作为该方法的返回值传递回来。如以下代码片断所示,result BitmapData 实例将使用与圆直径相同的宽度和高度创建,且不透明(第三个参数为 false),用颜色 0x808080(中灰色)预先填充: var result:BitmapData = new BitmapData(diameter, diameter, false, 0x808080); 接下来,该代码使用两个循环遍历图像的每个像素。外部循环从左到右遍历图像的每一列(使用变量 i 表示当前所操作的像素的水平位置),而内部循环从上到下遍历当前列的每个像素(使用变量 j 表示当前像素的垂直位置)。下面显示了循环的代码(省略了内部循环的内容): for (var i:int = 0; i < diameter; i++) { for (var j:int = 0; j < diameter; j++) { ... } } 由于循环逐个遍历像素,因此会为每个像素计算一个值(映射图像中该像素的颜色值)。此过程包含四个步骤:
|
|