點陣圖範例:動畫轉動的月球Flash Player 9 以及更新的版本,Adobe AIR 1.0 以及更新的版本 轉動的月球動畫範例示範了使用 Bitmap 物件和點陣圖影像資料 (BitmapData 物件) 的技術。該範例會使用月球表面的平面化影像做為原始影像資料,建立轉動、球面的月球動畫。當中將示範下列技術:
若要取得此樣本的應用程式檔案,請參閱 www.adobe.com/go/learn_programmingAS3samples_flash_tw。您可以在 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,實際啟動載入程序。在第二行中,設定將在影像完全載入時觸發的事件偵聽程式。請注意,addEventListener() 方法不是對 Loader 實體本身呼叫,而是對 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 屬性,參照到觸發事件的物件 (此例中,即為 addEventListener() 方法所呼叫的 LoaderInfo 實體,如先前的內容所提及)。接著,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 一樣,在 imageLoadComplete() 方法內會建立 sphere 物件,並填入其初始的影像資料,其中使用了下列程式碼: 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() 方法。最後,timer 實際上是由呼叫其 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 物件。該影像的建立方式將於設定像素值來建立點陣圖影像中說明。其他參數描述在已套用濾鏡影像中濾鏡的套用位置、將使用哪些顏色色版來控制置換效果,以及它們影響置換的程度。一旦建立了置換對應濾鏡,便會將其套用至 sphere (仍舊在 imageLoadComplete() 方法內): 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++) { ... } } 隨著迴圈一個接一個對像素執行循環,使得每一個像素都會計算出一個值 (對應影像內像素的顏色值)。此程序包含四個步驟:
|
|