Esempio di bitmap: animazione di rotazione della lunaFlash Player 9 e versioni successive, Adobe AIR 1.0 e versioni successive L'esempio di animazione di rotazione della luna mostra le tecniche per lavorare con gli oggetti Bitmap e i dati immagine bitmap (oggetti BitmapData). Nell'esempio viene illustrato come creare un'animazione di una luna sferica rotante utilizzando i dati originari di un'immagine piatta della superficie lunare. Verranno mostrate le seguenti tecniche:
Per ottenere i file dell'applicazione per questo esempio, visitate la pagina www.adobe.com/go/learn_programmingAS3samples_flash_it. I file di applicazione dell'animazione di rotazione della luna si trovano nella cartella Samples/SpinningMoon. L'applicazione è composta dai seguenti file:
Caricamento di un'immagine esterna come dati bitmapLa prima attività da compiere per questo esempio è caricare un file di immagine esterno, contenente un'immagine della superficie lunare. L'operazione di caricamento è gestita da due metodi nella classe MoonSphere: la funzione di costruzione MoonSphere(), dove viene avviato il processo di caricamento, e il metodo imageLoadComplete(), che viene chiamato una volta caricata completamente l'immagine esterna. Il procedimento per caricare un'immagine esterna è simile a quello per caricare un SWF esterno; entrambi utilizzano un'istanza della classe flash.display.Loader per effettuare l'operazione di caricamento. Il codice del metodo MoonSphere() che avvia il caricamento dell'immagine è il seguente: var imageLoader:Loader = new Loader();
imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageLoadComplete);
imageLoader.load(new URLRequest("moonMap.png"));
La prima riga dichiara l'istanza di caricamento denominata imageLoader. La terza riga è quella che avvia effettivamente il processo di caricamento chiamando il metodo load() dell'oggetto Loader, assegnando un'istanza URLRequest corrispondente all'URL dell'immagine da caricare. La seconda riga imposta il listener dell'evento che viene attivato al termine del caricamento dell'immagine. Notare che il metodo addEventListener() non viene chiamato sull'istanza del Loader, ma sulla proprietà contentLoaderInfo dell'oggetto Loader. L'istanza del Loader in sé non invia eventi relativi al contenuto che viene caricato. La sua proprietà contentLoaderInfo, tuttavia, contiene un riferimento all'oggetto LoaderInfo che è associato al contenuto che viene caricato nell'oggetto Loader (in questo caso l'immagine esterna). Tale oggetto LoaderInfo fornisce diversi eventi relativi al progresso e al completamento del caricamento del contenuto esterno, compreso l'evento complete (Event.COMPLETE) che avvia una chiamata al metodo imageLoadComplete() al termine del caricamento dell'immagine. Se l'avvio del caricamento dell'immagine esterna è una parte importante del processo, è altrettanto importante sapere cosa fare alla fine del caricamento. Come mostrato nel codice riportato sopra, al termine del caricamento dell'immagine viene chiamata la funzione imageLoadComplete(). Questa funzione effettua una serie di operazioni sui dati dell'immagine caricata, che verranno descritti in seguito. Ad ogni modo, la prima cosa da fare per utilizzare i dati dell'immagine è accedervi. Quando viene utilizzato un oggetto Loader per caricare un'immagine esterna, l'immagine caricata diventa un'istanza Bitmap che viene associata all'oggetto Loader come oggetto di visualizzazione secondario. In questo caso, l'istanza Loader viene resa disponibile per il metodo listener di evento come parte dell'oggetto evento che viene assegnato al metodo come parametro. Le prime righe del metodo imageLoadComplete() sono le seguenti: private function imageLoadComplete(event:Event):void
{
textureMap = event.target.content.bitmapData;
...
}
Notate che il parametro dell'oggetto evento è denominato event ed è un'istanza della classe Event. Tutte le istanze della classe Event hanno una proprietà target che si riferisce agli oggetti che lanciano l'evento (in questo caso l'istanza LoaderInfo sulla quale il metodo addEventListener() era stato chiamato, come descritto in precedenza). L'oggetto LoaderInfo, a sua volta, ha una proprietà content che (una volta completato il processo di caricamento) contiene l'istanza Bitmap dell'immagine bitmap caricata. Se desiderate visualizzare l'immagine direttamente su schermo, potete includere questa istanza Bitmap (event.target.content) in un contenitore di oggetti di visualizzazione. In alternativa, è possibile anche includere l'oggetto Loader in un contenitore di oggetti di visualizzazione. In questo esempio, tuttavia, il contenuto caricato viene utilizzato come origine dei dati immagine originari invece che essere utilizzato per la visualizzazione su schermo. Di conseguenza, la prima riga del metodo imageLoadComplete() legge la proprietà bitmapData dell'istanza Bitmap caricata (event.target.content.bitmapData) e la memorizza nella variabile di istanza denominata textureMap, la quale, viene utilizzata come origine dei dati dell'immagine per creare l'animazione della rotazione della luna. Questo è descritto in seguito. Creazione di un'animazione mediante copiatura dei pixelUna definizione base di animazione è l'illusione di movimento creata dal cambiamento di un'immagine nel tempo. In questo esempio, l'obiettivo è creare l'illusione di una luna sferica che ruota attorno al suo asse verticale. Tuttavia, per le finalità dell'animazione, è possibile ignorare gli aspetti di distorsione sferica dell'esempio. Soffermatevi sull'immagine effettivamente caricata e utilizzata come origine dei dati per l'immagine della luna: Come potete notare, l'immagine non è composta da una o più sfere; si tratta di una fotografia di forma rettangolare della superficie lunare. Poiché la foto è stata scattata esattamente sull'equatore lunare, le parti dell'immagine in prossimità dei margini superiore e inferiore dell'immagine sono allungati e distorti. Più avanti verrà illustrato come utilizzare un filtro mappa di spostamento per rimuovere questa distorsione dall'immagine e ottenere una forma sferica. Tuttavia, poiché l'immagine di origine è rettangolare, per creare l'illusione che la sfera stia ruotando è sufficiente che il codice faccia scorrere la superficie lunare orizzontalmente. Notate che l'immagine contiene due copie affiancate della fotografia della superficie lunare. Questa immagine è l'immagine di origine dalla quale i dati dell'immagine vengono ripetutamente copiati per creare l'illusione del movimento. L'affiancamento di due copie della stessa immagine consente di creare un effetto di scorrimento continuo e ininterrotto in modo più semplice. Verrà ora esaminato il funzionamento del processo di animazione passo per passo. Il processo comprende due oggetti ActionScript separati. Per prima vi è l'immagine di origine caricata, che nel codice è rappresentata dall'istanza BitmapData denominata textureMap. Come descritto in precedenza, textureMap viene compilata con i dati immagine non appena viene caricata l'immagine esterna, utilizzando il seguente codice: textureMap = event.target.content.bitmapData; Il contenuto di textureMap è l'immagine rettangolare della luna. Inoltre, per creare la rotazione, nel codice viene utilizzata un'istanza Bitmap denominata sphere, che corrisponde all'oggetto di visualizzazione che mostra l'immagine della luna su schermo. Come textureMap, l'oggetto sphere viene creato e compilato con i suoi dati immagine iniziali nel metodo imageLoadComplete(), utilizzando il seguente codice: 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));
Come mostrato nel codice, viene creata un'istanza per sphere. La sua proprietà bitmapData (i dati immagine originari visualizzati da sphere) viene creata con la stessa altezza di textureMap, ma con una larghezza pari alla sua metà. In altre parole, il contenuto di sphere avrà le dimensioni di una sola foto della luna (poiché l'immagine textureMap contiene due foto della luna affiancate). Successivamente la proprietà bitmapData viene riempita con i dati dell'immagine grazie al metodo copyPixels(). I parametri del metodo copyPixels() possono indicare diverse cose:
Rappresentato visivamente, il codice copia i pixel da textureMap evidenziati nell'immagine seguente e li incolla in sphere. In altre parole, il contenuto BitmapData di sphere è la porzione di textureMap evidenziata nell'immagine in basso: Questo, tuttavia, è soltanto lo stato iniziale di sphere, ovvero il primo contenuto di dati immagine copiato in sphere. Dopo il caricamento dell'immagine di origine e la creazione di sphere, l'attività finale effettuata dal metodo imageLoadComplete() è quella di impostare l'animazione. L'animazione è gestita da un'istanza Timer denominata rotationTimer, che viene creata e avviata dal codice seguente: var rotationTimer:Timer = new Timer(15); rotationTimer.addEventListener(TimerEvent.TIMER, rotateMoon); rotationTimer.start(); Il codice crea per prima cosa l'istanza Timer denominata rotationTimer; il parametro assegnato alla funzione di costruzione Timer() indica che rotationTimer deve avviare il suo evento timer ogni 15 millisecondi. Successivamente viene chiamato il metodo addEventListener() con l'indicazione che quando si verifica l'evento timer (TimerEvent.TIMER), viene chiamato il metodo rotateMoon(). Alla fine il timer viene avviato chiamando il relativo metodo start(). Grazie al modo in cui rotationTimer è definito, Flash Player chiama il metodo rotateMoon() approssimativamente una volta ogni 15 millisecondi nella classe MoonSphere, che è la posizione in cui si verifica l'animazione della luna. Il codice sorgente del metodo rotateMoon() è il seguente: 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();
}
Il codice effettua tre operazioni:
Questo codice viene chiamato ripetutamente, una volta ogni 15 millisecondi. Poiché la posizione del rettangolo di origine viene continuamente spostata e i pixel vengono copiati in sphere, su schermo verrà visualizzata la foto della luna rappresentata da sphere, in continuo scorrimento. In altre parole sembrerà che la luna ruoti costantemente. Creazione dell'aspetto sfericoLa luna, naturalmente, è una sfera e non un rettangolo. Di conseguenza, nel corso dell'animazione continua è necessario convertire la foto rettangolare della superficie lunare in sfera. Questa operazione richiede due passaggi distinti: la creazione di una maschera per nascondere tutto il contenuto tranne un'area circolare della foto della superficie lunare, e la creazione di un filtro mappa di spostamento per distorcere l'aspetto della foto della luna in modo che appaia tridimensionale. Per prima cosa, si utilizza una maschera di forma circolare per nascondere tutto il contenuto dell'oggetto MoonSphere con l'eccezione dell'area circolare creata dal filtro. Il seguente codice crea la maschera come istanza Shape e la applica come maschera all'istanza MoonSphere: moonMask = new Shape(); moonMask.graphics.beginFill(0); moonMask.graphics.drawCircle(0, 0, radius); this.addChild(moonMask); this.mask = moonMask; Notate che, poiché MoonSphere è un oggetto di visualizzazione (è basato sulla classe Sprite), è possibile applicare la maschera direttamente all'istanza MoonSphere utilizzando la sua proprietà mask ereditata. ![]() Nascondere alcune parti della foto utilizzando una maschera di forma circolare non è sufficiente per creare un effetto di rotazione sferica realistico. A causa del modo in cui è stata scattata la foto della superficie lunare, le dimensioni della stessa non sono proporzionali; le porzioni dell'immagine che si trovano in prossimità dei margini superiore e inferiore dell'immagine risultano più distorte e allungate rispetto alle parti che si trovano all'equatore. Per distorcere l'aspetto della foto e farla sembrare tridimensionale viene utilizzato un filtro mappa di spostamento. Un filtro mappa di spostamento è un tipo di filtro utilizzato per distorcere un'immagine. In questo caso, per farla sembrare più realistica, la distorsione della foto della luna si ottiene schiacciando orizzontalmente la parte alta e quella bassa dell'immagine, e lasciando invariata la parte centrale. Poiché il filtro agisce su una porzione quadrata della foto, schiacciando la parte alta e quella bassa, ma non la parte centrale, il risultato sarà un cerchio. Un effetto secondario dell'animazione di questa immagine distorta è che la parte centrale dell'immagine sembra coprire una distanza maggiore in pixel rispetto alle aree vicine alla parte alta e a quella bassa; questo crea l'illusione che il cerchio sia un oggetto tridimensionale (una sfera). Il seguente codice viene utilizzato per creare il filtro mappa di spostamento, denominato displaceFilter: var displaceFilter:DisplacementMapFilter;
displaceFilter = new DisplacementMapFilter(fisheyeLens,
new Point(radius, 0),
BitmapDataChannel.RED,
BitmapDataChannel.GREEN,
radius, 0);
Il primo parametro, fisheyeLens, è detto immagine della mappa; in questo caso è un oggetto BitmapData che viene creato a livello di codice. La creazione di tale immagine è descritta in Creazione di un'immagine bitmap mediante l'impostazione dei valori dei pixel. Gli altri parametri descrivono la posizione all'interno dell'immagine filtrata in cui il filtro deve essere applicato, quali canali di colore devono essere utilizzati per controllare l'effetto di spostamento e fino a che punto influiranno sullo spostamento. Dopo la creazione del filtro mappa di spostamento questo viene applicato a sphere, sempre all'interno del metodo imageLoadComplete(): sphere.filters = [displaceFilter]; L'immagine finale, con l'applicazione della maschera e del filtro mappa di spostamento, avrà questo aspetto: ![]() A ogni ciclo dell'animazione della rotazione lunare, il contenuto BitmapData di sphere viene sovrascritto da una nuova istantanea dei dati dell'immagine di origine. Tuttavia, non è necessario riapplicare il filtro ogni volta. Questo perché il filtro viene applicato all'istanza Bitmap (l'oggetto di visualizzazione) e non ai dati bitmap (le informazioni originarie sui pixel). Tenete presente che l'istanza Bitmap non coincide con gli effettivi dati bitmap: si tratta solo di un oggetto di visualizzazione che mostra su schermo i dati bitmap. Per fare un'analogia, l'istanza Bitmap è come un proiettore usato per la proiezione di diapositive sullo schermo, mentre l'oggetto BitmapData è come la diapositiva vera e propria che può essere vista attraverso il proiettore. I filtri possono essere applicati direttamente a un oggetto BitmapData; nella nostra analogia questo significherebbe applicare un filtro direttamente sulla diapositiva per alterare l'immagine. I filtri possono tuttavia essere applicati anche a qualsiasi oggetto di visualizzazione, comprese le istanze Bitmap; questo può essere paragonato al mettere un filtro davanti alla lente del proiettore per distorcere la proiezione sullo schermo (senza alterare la diapositiva originale). Poiché i dati bitmap originari sono accessibili tramite una proprietà BitmapData dell'istanza Bitmap, si sarebbe potuto applicare il filtro direttamente su quei dati. Tuttavia, in questo caso ha più senso applicare il filtro all'oggetto di visualizzazione Bitmap anziché ai dati bitmap. Per informazioni dettagliate riguardo all'uso del filtro mappa di spostamento in ActionScript, fate riferimento a Filtraggio degli oggetti di visualizzazione. Creazione di un'immagine bitmap mediante l'impostazione dei valori dei pixelUn aspetto importante del filtro mappa di spostamento è che agisce su due immagini. La prima, l'immagine di origine, è l'immagine effettivamente alterata dal filtro. In questo esempio, l'immagine di origine è l'istanza Bitmap denominata sphere. L'altra immagine utilizzata dal filtro è detta immagine mappa. L'immagine mappa non viene realmente visualizzata su schermo. Il colore di ognuno dei suoi pixel, infatti, è utilizzato come input per la funzione di spostamento: il colore del pixel che si trova in corrispondenza di certe coordinate x e y nell'immagine mappa determina la quantità di spostamento (spostamento fisico in posizione), che deve essere applicata al pixel corrispondente alle coordinate x e y dell'immagine di origine. Di conseguenza, per utilizzare il filtro mappa di spostamento per creare un effetto sfera è necessario avere un'immagine mappa appropriata: un'immagine che abbia uno sfondo grigio e un cerchio che sia riempito con le tonalità di un singolo colore (rosso) che sfumino orizzontalmente da quelle più scure a quelle più chiare, come mostrato nella figura: ![]() Poiché in questo esempio vengono utilizzate solo un'immagine mappa e un filtro, l'immagine mappa viene creata una sola volta, nel metodo imageLoadComplete() (in altre parole al termine del caricamento dell'immagine esterna). L'immagine mappa (fisheyeLens) viene creata chiamando il metodo createFisheyeMap() della classe MoonSphere: var fisheyeLens:BitmapData = createFisheyeMap(radius); All'interno del metodo createFisheyeMap() viene tracciata l'immagine mappa, un pixel alla volta, utilizzando il metodo setPixel() della classe BitmapData. Il codice completo per il metodo createFisheyeMap() è riportato di seguito, accompagnato da un'analisi passo per passo del suo funzionamento: 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;
}
Per prima cosa quando il metodo viene chiamato riceve un parametro, radius, che indica il raggio dell'immagine circolare da creare. Quindi il codice crea l'oggetto BitmapData sul quale verrà tracciato il cerchio. Questo oggetto denominato result viene alla fine restituito come risultato del metodo. Come mostrato nel seguente snippet di codice, il valore result dell'istanza BitmapData viene creato con una larghezza e un'altezza pari al diametro del cerchio, senza trasparenza (il terzo parametro è impostato su false) e riempito con il colore 0x808080 (grigio): var result:BitmapData = new BitmapData(diameter,
diameter,
false,
0x808080);
In seguito il codice utilizza due cicli da ripetere per ogni pixel dell'immagine. Il ciclo esterno esamina ogni colonna da sinistra a destra (servendosi della variabile i per rappresentare la posizione orizzontale del pixel manipolato al momento), mentre il ciclo interno esamina ogni pixel della colonna attiva dall'alto verso il basso (per mezzo della variabile j che rappresenta la posizione verticale del pixel attivo). Il codice per i cicli (il contenuto del ciclo interno è omesso) è riportato di seguito: for (var i:int = 0; i < diameter; i++)
{
for (var j:int = 0; j < diameter; j++)
{
...
}
}
Man mano che i cicli esaminano i pixel uno ad uno, viene calcolato un valore per ogni pixel (il valore del colore di quel pixel nell'immagine mappa). Questo processo richiede quattro passaggi:
|
|