Przykład animowanego wirującego księżyca ilustruje techniki pracy z obiektami Bitmap oraz z danymi obrazów bitmapowych (obiektami BitmapData). Ten przykład ilustruje tworzenie animacji ruchu wirowego sferycznego księżyca za pomocą płaskiego obrazu powierzchni księżyca wykorzystywanego jako dane nieprzetworzone. Poniższe techniki ilustrują:
-
Ładowanie obrazu zewnętrznego i uzyskiwanie dostępu do jego danych nieprzetworzonych
-
Tworzenie animacji przez powtarzanie kopiowania pikseli z innej części obrazu źródłowego
-
Tworzenie obrazu bitmapowego przez ustawienie wartości pikseli
Aby pobrać pliki tej przykładowej aplikacji, należy przejść na stronę
www.adobe.com/go/learn_programmingAS3samples_flash_pl
. Pliki aplikacji Animowany wirujący księżyc można znaleźć w folderze Samples/SpinningMoon. Aplikacja składa się z następujących plików:
File
|
Opis
|
SpinningMoon.mxml
lub
SpinningMoon.fla
|
Główny plik aplikacji we Flex (MXML) lub Flash (FLA).
|
com/example/programmingas3/moon/MoonSphere.as
|
Klasa udostępniająca funkcjonalności takie jak ładowanie, wyświetlanie i animowanie księżyca.
|
moonMap.png
|
Plik obrazu zawierający zdjęcie powierzchni księżyca, ładowany i animowany w celu utworzenia animowanego, wirującego księżyca.
|
Ładowanie obrazu zewnętrznego w formie danych bitmapy
Pierwszym, głównym zadaniem w tym przykładzie jest załadowanie zewnętrznego pliku obrazu, stanowiącego zdjęcie powierzchni księżyca. Operacja ładowania odbywa się za pomocą dwu metod klasy MoonSphere: konstruktora
MoonSphere()
, gdzie inicjowany jest proces ładowania, metody
imageLoadComplete()
, która jest wywoływana po całkowitym załadowaniu obrazu zewnętrznego.
Ładowanie obrazu zewnętrznego przypomina ładowanie zewnętrznego pliku SWF; w obu przypadkach do wykonania operacji ładowania używana jest instancja klasy flash.display.Loader. Rzeczywisty kod w metodzie
MoonSphere()
, która rozpoczyna ładowanie obrazu, jest następujący:
var imageLoader:Loader = new Loader();
imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageLoadComplete);
imageLoader.load(new URLRequest("moonMap.png"));
W pierwszej linii znajduje się deklaracja instancji Loader o nazwie
imageLoader
. Dopiero trzecia linia faktycznie rozpoczyna proces ładowania, wywołując metodę obiektu Loader
load()
, i przekazując instancję URLRequest reprezentującą adres URL obrazu do załadowania. Druga linia powoduje ustawienie detektora zdarzeń, który zostanie wyzwolony w przypadku całkowitego załadowania obrazu. Należy zauważyć, że metoda
addEventListener()
nie jest wywoływana w instancji Loader samoistnie; zamiast tego jest ona wywoływana w zależności od właściwości
contentLoaderInfo
obiektu Loader. Instancja Loader nie przekazuje samoistnie zdarzeń związanych z ładowaną zawartością. Jej właściwość
contentLoaderInfo
zawiera jednak odniesienie do obiektu LoaderInfo, który jest skojarzony z ładowaną do obiektu Loader zawartością (w tym wypadku z obrazem zewnętrznym). Ten obiekt LoaderInfo udostępnia kilka zdarzeń związanych z postępem i ukończeniem ładowania zawartości zewnętrznej, w tym ze zdarzeniem
complete
(
Event.COMPLETE
), które wyzwala wywołanie metody
imageLoadComplete()
po całkowitym załadowaniu obrazu.
Chociaż uruchomienie ładowania obrazu zewnętrznego stanowi ważną część procesu, tak samo ważna jest wiedza, co należy zrobić po ukończeniu ładowania. Zgodnie z kodem pokazanym powyżej funkcja
imageLoadComplete()
jest wywoływana po załadowaniu obrazu. Funkcja ta odpowiada za kilka kwestii związanych z danymi ładowanego obrazu, opisanymi w dalszej części podręcznika. Jednak aby można było użyć danych obrazu, funkcja ta musi mieć do nich dostęp. W przypadku korzystania z obiektu Loader do ładowania obrazu zewnętrznego ładowany obraz staje się instancją Bitmap dołączoną jako potomny obiekt wyświetlania do obiektu Loader. W takim przypadku dla metody detektora zdarzeń dostępna jest instancja Loader przekazywana następnie do metody jako parametr. Pierwsze wiersze metody
imageLoadComplete()
są następujące:
private function imageLoadComplete(event:Event):void
{
textureMap = event.target.content.bitmapData;
...
}
Należy zauważyć, że parametr obiektu zdarzenia ma nazwę
event
, oraz że jest to instancja klasy Event. Każda instancja klasy Event ma właściwość
target
, która odnosi się do obiektu wyzwalającego zdarzenie (w tym przypadku, jest to instancja LoaderInfo, dla której wywołano metodę
addEventListener()
, zgodnie z opisem powyżej). Obiekt LoaderInfo z kolei ma właściwość
content
, która (po ukończeniu procesu ładowania) zawiera instancję Bitmap z załadowanym obrazem bitmapowym. W celu wyświetlenia obrazu bezpośrednio na ekranie można wówczas dołączyć tę instancję Bitmap (
event.target.content
) do kontenera obiektu wyświetlanego. (Można również dołączyć obiekt Loader do kontenera obiektu wyświetlanego). W tym przypadku jednak załadowana treść stanowi źródło nieprzetworzonych danych obrazu, nie jest zaś wyświetlana na ekranie. Konsekwentnie, pierwsza linia metody
imageLoadComplete()
odczytuje właściwość
bitmapData
załadowanej instancji Bitmap (
event.target.content.bitmapData
) i zapisuje ją w zmiennej instancji o nazwie
textureMap
, która stanowi źródło danych obrazu użytych do utworzenia animacji obracającego się księżyca. To zagadnienie zostało opisane w następnej sekcji.
Tworzenie animacji przez kopiowanie pikseli
Podstawowa definicja animacji to złudzenie ruchu, lub zmiany, utworzone w wyniku zmiany obrazu w czasie. W tym przykładzie celem jest utworzenie iluzji wirowania sferycznego księżyca wokół jego pionowej osi. Dla celów tej animacji można jednak zignorować kwestię zniekształcenia sferycznego w tym przykładzie. Należy uwzględnić rzeczywisty obraz ładowany jako źródło i używany w charakterze źródła dla danych obrazu księżyca:
Jak można zobaczyć, obraz nie ma kształtu sfery ani kilku sfer; jest to tylko prostokątna fotografia powierzchni księżyca. Ponieważ jednak zdjęcie zostało zrobione dokładnie na równiku księżyca, części obrazu w górnej i dolnej jego części są rozciągnięte i zniekształcone. W celu usunięcia zniekształceń i uzyskania kształtu sferycznego użyjemy filtra mapy przemieszczeń, opisanego poniżej. Ponieważ jednak obraz źródłowy ma kształt prostokąta, w celu wytworzenia iluzji, że sfera się obraca, konieczne jest po prostu przesunięcie zdjęcia powierzchni księżyca w poziomie przez kod.
Należy przy tym zauważyć, że obraz w istocie zawiera dwie kopie powierzchni księżyca, ułożone obok siebie. Obraz ten jest obrazem źródłowym, z którego dane obrazu są kopiowane w sposób cykliczny w celu utworzenia efektu ruchu. Dysponując umieszczonymi obok siebie dwiema kopiami obrazu, można łatwiej uzyskać niezakłócony niczym efekt przewijania. Przejdźmy teraz krok po kroku przez proces animacji, aby zobaczyć, jak to działa.
Proces w istocie obejmuje dwa różne obiekty ActionScript. Pierwszy z nich to ładowany obraz źródłowy, reprezentowany w kodzie przez instancję BitmapData o nazwie
textureMap
. Zgodnie ze wcześniejszym opisem, dane obrazu są wypełniane instancją
textureMap
, gdy tylko zewnętrzny obraz zostanie załadowany z wykorzystaniem tego kodu:
textureMap = event.target.content.bitmapData;
Zawartość obiektu
textureMap
jest prostokątnym obrazem księżyca. Ponadto, w celu utworzenia animowanego ruchu w przykładzie zastosowano instancję Bitmap o nazwie
sphere
, która jest rzeczywistym obiektem wyświetlanym — księżycem. Podobnie jak
textureMap
, obiekt
sphere
jest tworzony i wypełniany początkowymi danymi obrazu w metodzie
imageLoadComplete()
za pomocą następującego kodu:
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));
Zgodnie z treścią kodu tworzona jest instancja
sphere
. Jej właściwość
bitmapData
(nieprzetworzone dane obrazu wyświetlane przez instancję
sphere
) jest tworzona przy użyciu tej samej wysokości oraz połowy szerokości instancji
textureMap
. Innymi słowy, zawartość instancji
sphere
będzie odpowiadała rozmiarom zdjęcia księżyca (ponieważ obraz
textureMap
zawiera dwa zdjęcia księżyca umieszczone obok siebie). Następnie właściwość
bitmapData
jest wypełniana danymi obrazu przy wykorzystaniu metody
copyPixels()
. Parametry należące do metody
copyPixels()
mogą stanowić o kilku kwestiach:
-
Pierwszy z parametrów wskazuje, że dane obrazu są kopiowane z instancji
textureMap
.
-
Drugi z parametrów, nowa instancja Rectangle, określa, z której części instancji
textureMap
należy wykonać migawkę obrazu; w tym przypadku migawką jest prostokąt rozpoczynający się w górnym lewym rogu instancji
textureMap
(oznaczonej przez dwa pierwsze parametry
Rectangle()
:
0, 0
) oraz szerokość i wysokość migawki odpowiadająca właściwościom
width
i
height
instancji
sphere
.
-
Trzeci z parametrów, nowa instancja Point charakteryzująca się wartościami x i y wynoszącymi
0
, definiuje miejsce docelowe danych pikseli — w tym przypadku, lewy górny róg (0, 0) elementu
sphere.bitmapData
.
Można opisać to tak, że kod kopiuje piksele z instancji
textureMap
opisanej na poniższym obrazie i wkleja je do instancji
sphere
. Innymi słowy, zawartość BitmapData instancji
sphere
jest fragmentem instancji
textureMap
(patrz podświetlenie):
Należy jednak pamiętać, że jest to tylko początkowy stan instancji
sphere
— zawartość pierwszego obrazu, kopiowana do instancji
sphere
.
Po załadowaniu obrazu źródłowego oraz utworzeniu instancji
sphere
ostatnim zadaniem wykonywanym przez metodę
imageLoadComplete()
jest skonfigurowanie animacji. Animacja jest sterowana przez instancję Timer zwaną
rotationTimer
, tworzoną i uruchamianą następującym kodem:
var rotationTimer:Timer = new Timer(15);
rotationTimer.addEventListener(TimerEvent.TIMER, rotateMoon);
rotationTimer.start();
Kod tworzy najpierw instancję Timer o nazwie
rotationTimer
; parametr ten, przekazywany do konstruktora
Timer()
, wskazuje, że instancja
rotationTimer
powinna wyzwalać zdarzenie
timer
co 15 milisekund. Następnie wywoływana jest metoda
addEventListener()
, określająca, że wystąpienie zdarzenia
timer
(
TimerEvent.TIMER
) powoduje wywołanie metody
rotateMoon()
. Ostatecznie instancja timer jest faktycznie uruchamiana przez wywołanie jej metody
start()
.
Ze względu na sposób, w jaki zdefiniowano instancję
rotationTimer
, co około 15 milisekund Flash Player wywołuje metodę
rotateMoon()
w klasie MoonSphere, to jest tam, gdzie faktycznie ma miejsce animacja księżyca.
Kod źródłowy metody rotateMoon()
brzmi następująco:
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();
}
Kod realizuje trzy funkcje:
-
Wartość zmiennej
sourceX
(początkowo ustawiona na wartość 0), z przyrostem co 1.
sourceX += 1;
Jak się dalej okaże, zmienna
sourceX
jest używana do określenia lokalizacji w instancji
textureMap
, z której będą kopiowane piksele do instancji
sphere
, tak, że kod ten będzie wywoływał przesunięcie prostokąta o jeden piksel na prawo w instancji
textureMap
. Wracając do reprezentacji wizualnej, po kilku cyklach animacji prostokąt źródłowy zostanie przesunięty o kilka pikseli na prawo, o tak:
Po kolejnych kilku cyklach prostokąt znajdzie się jeszcze dalej:
To stopniowe, niezakłócone niczym przesuwanie się miejsca, z której kopiowane są piksele, jest kluczem do utworzenia animacji. Powoli, lecz w sposób ciągły przesuwając lokalizację źródłową na prawo, powodujemy ciągłe przesuwanie się obrazu wyświetlanego na ekranie w instancji
sphere
na lewo. Jest to przyczyną dla której konieczne staje się posiadanie dwu kopii obrazu źródłowego (
textureMap
), będącego zdjęciem powierzchni księżyca. Ze względu na to, że prostokąt w sposób ciągły przemieszcza się na prawo, przez większość czasu nie znajduje się on nad pojedynczym zdjęciem księżyca, a raczej zachodzi na oba zdjęcia.
-
Powolny ruch prostokąta źródłowego na prawo stwarza tylko jeden problem. Ostatecznie prostokąt osiągnie prawą krawędź instancji
textureMap
i wyjdzie poza zakres pikseli zdjęcia księżyca kopiowanych do instancji
sphere
:
Kolejne wiersze kodu stanowią rozwiązanie tego problemu:
if (sourceX >= textureMap.width / 2)
{
sourceX = 0;
}
Kod kontroluje, czy
sourceX
(lewa krawędź prostokąta) osiągnęła środek instancji
textureMap
. Jeśli tak, resetuje ona
sourceX
z powrotem na 0, cofając ją do lewej krawędzi instancji
textureMap
; cykl rozpoczyna się od nowa:
-
Po obliczeniu odpowiedniej wartości
sourceX
ostatnim krokiem utworzenia animacji jest faktyczne skopiowanie nowych pikseli prostokąta źródłowego do instancji
sphere
. Kod umożliwiający to jest bardzo podobny do kodu odpowiadającego na samym początku za wypełnienie instancji
sphere
(jak opisano w powyższych sekcjach); jedyną różnicę stanowi fakt, że w tym przypadku wywołanie konstruktora
new Rectangle()
powoduje umieszczenie lewej krawędzi prostokąta w miejscu
sourceX
:
sphere.bitmapData.copyPixels(textureMap,
new Rectangle(sourceX, 0, sphere.width, sphere.height),
new Point(0, 0));
Należy pamiętać, że ten kod jest wywoływany cyklicznie, w odstępach 15-milisekundowych. w miarę przesuwania lokalizacji prostokąta źródłowego oraz kopiowania pikseli do instancji
sphere
na ekranie widać, jak zdjęcie księżyca reprezentowane przez instancję
sphere
przesuwa się. Innymi słowy, wygląda to tak, jakby księżyc stale się obracał.
Tworzenie efektu sferycznego
Księżyc to oczywiście sfera, a nie prostokąt. W naszym przykładzie konieczne jest zatem użycie prostokątnego zdjęcia powierzchni księżyca, w stanie ciągłej animacji, i przekonwertowanie go do formy sferycznej. Wymaga to wykonania dwu osobnych kroków: użycia maski do ukrycia wszystkiego poza okrągłym obszarem zdjęcia powierzchni księżyca, oraz filtra mapy przemieszczenia służącego do odkształcenia zdjęcia księżyca tak, aby wyglądało ono na trójwymiarowe.
Po pierwsze, maska o kształcie okręgu służy do przesłonięcia całej zawartości obiektu MoonSphere, z wyjątkiem sfery utworzonej przez filtr. Poniższy kod umożliwia utworzenie maski w postaci instancji Shape, oraz zastosowanie jej w formie maski instancji MoonSphere:
moonMask = new Shape();
moonMask.graphics.beginFill(0);
moonMask.graphics.drawCircle(0, 0, radius);
this.addChild(moonMask);
this.mask = moonMask;
Należy pamiętać, że ponieważ MoonSphere jest obiektem wyświetlanym (opartym na klasie Sprite), maskę można nałożyć bezpośrednio na instancję MoonSphere za pomocą jej dziedziczonej właściwości
mask
.
Do utworzenia realistycznego efektu wirującej sfery nie wystarczy niestety proste ukrycie fragmentu zdjęcia. Z uwagi na sposób, w jaki zrobiono zdjęcie księżyca, jego wymiary są nieproporcjonalne; fragmenty obrazu przesunięte w górę oraz w dół obrazy są znacznie bardziej zniekształcone i wyciągnięte w porównaniu z fragmentami w obszarze równika. W celu odkształcenia zdjęcia księżyca tak, aby miało ono charakter trójwymiarowy, użyjemy filtra mapy przemieszczeń.
Filtr mapy przemieszczeń jest to rodzaj filtra używany do odkształcania obrazu. W naszym przypadku zdjęcie księżyca zostanie odkształcone tak, aby wyglądało bardziej „realistycznie”. W tym celu górna i dolna część obrazu zostaną ściśnięte w poziomie, podczas gdy środkowa część obrazu pozostanie niezmieniona. Przyjmując, że filtr działa na fragmencie o kształcie kwadratu, ściśnięcie górnej i dolnej części, a pozostawienie niezmienionej środkowej części, umożliwi uzyskanie kształtu okręgu. Efektem „ubocznym” w przypadku tej akurat animacji jest fakt, że środek obrazu wydaje się przemieszczać znacznie dalej, niż obszary w górnej i dolnej części — to złudzenie dodatkowo wzmacnia efekt trójwymiarowości (złudzenie sferyczności księżyca).
Poniższy kod służy do utworzenia filtra mapy przemieszczeń o nazwie
displaceFilter
:
var displaceFilter:DisplacementMapFilter;
displaceFilter = new DisplacementMapFilter(fisheyeLens,
new Point(radius, 0),
BitmapDataChannel.RED,
BitmapDataChannel.GREEN,
radius, 0);
Pierwszy z parametrów,
fisheyeLens
, jest znany jako obraz mapy; w tym przypadku jest to obiekt BitmapData tworzony programowo. Tworzenie obrazu opisano w sekcji
Tworzenie obrazu bitmapowego przez ustawienie wartości pikseli
. Pozostałe parametry opisują położenie filtrowanego obrazu, w którym powinien zostać nałożony filtr, oraz to, które kanały kolorów zostaną użyte do kontroli efektu przemieszczenia, a także, w jakim stopniu będą one miały wpływ na przemieszczenie. Po utworzeniu filtra mapy przemieszczeń jest on nakładany na instancję
sphere
(nadal w metodzie
imageLoadComplete()
):
sphere.filters = [displaceFilter];
Ostateczny obraz, wraz z maską i filtrem mapy przemieszczeń wygląda następująco:
Wraz z każdym cyklem animacji wirującego księżyca zawartość obiektu BitmapData sfery jest nadpisywana nową migawką danych obrazu źródłowego. Filtr nie wymaga jednak ponownego zastosowania za każdym razem. Dzieje się tak, ponieważ filtr jest nakładany na instancję Bitmap (obiekt wyświetlany), nie zaś na dane bitmapowe (nieprzetworzone informacje o pikselach). Należy przy tym pamiętać, że instancja Bitmap nie stanowi faktycznych danych o bitmapie; jest to obiekt wyświetlany przedstawiający dane bitmapy na ekranie. Posługując się analogią, instancję Bitmap można przyrównać do projektora slajdów wyświetlającego je na ekranie, a obiekt BitmapData do faktycznego slajdu, który można wyświetlić za pomocą projektora. Filtr można nałożyć bezpośrednio na obiekt BitmapData, co byłoby równoważne rysowaniu po samym slajdzie. Można również nałożyć filtr tylko na obiekt wyświetlany, w tym na instancję Bitmap; oznaczałoby to umieszczenie filtra przed obiektywem rzutnika, tak aby działanie filtra dotyczyło wyłącznie obrazu wyświetlanego na ekranie (bez ingerencji w oryginalne zdjęcie). Ponieważ nieprzetworzone dane bitmapowe są dostępne za pośrednictwem instancji Bitmap właściwości bitmapData, filtr może zostać nałożony bezpośrednio na nieprzetworzone dane bitmapy. W tym przypadku sens ma raczej nałożenie filtra na obiekt wyświetlany Bitmap niż na dane bitmapy.
Szczegółowe informacje dotyczące używania filtra mapy przemieszczeń języka ActionScript można znaleźć w sekcji
Filtrowanie obiektów wyświetlanych
.
Tworzenie obrazu bitmapowego przez ustawienie wartości pikseli
Jedną z istotnych kwestii dotyczących filtra mapy przemieszczeń jest to, że w rzeczywistości wymaga on użycia dwu obrazów. Jeden z nich, obraz źródłowy, jest obrazem naprawdę zmienionym w wyniku działania filtru. W tym przykładzie obraz źródłowy jest to instancja obiektu Bitmap pod nazwą
sphere
. Drugi obraz używany przez filtr zwany jest obrazem mapy. Obraz mapy nie jest wyświetlany na ekranie. Zamiast tego, kolor każdego z jego pikseli jest używany jako dane wejściowe dla funkcji przemieszczania — kolor piksela o danych współrzędnych x, y na obrazie mapy determinuje, jak duże przemieszczenie (fizyczne przesunięcie) jest stosowane wobec piksela o tych współrzędnych x, y w obrazie źródłowym.
Konsekwentnie, w celu użycia filtra mapy przemieszczeń do utworzenia efektu sferycznego konieczne jest zastosowanie odpowiedniego obrazu mapy — o szarym tle i okręgu z wypełnieniem gradientowym jednego koloru (czerwonego) rozjaśniającym się w poziomie, tak jak przedstawiono poniżej:
Ponieważ w przykładzie tym użyto wyłączeni jednego obrazu mapy i filtra, obraz mapy jest tworzony tylko raz, w metodzie
imageLoadComplete()
(innymi słowy, po zakończeniu ładowania obrazu zewnętrznego). Tworzony jest obraz mapy o nazwie
fisheyeLens
przez wywołanie klasy MoonSphere metody
createFisheyeMap()
:
var fisheyeLens:BitmapData = createFisheyeMap(radius);
Wewnątrz metody
createFisheyeMap()
odbywa się faktyczne kreślenie obrazu mapy, po jednym pikselu naraz, z wykorzystaniem metody
setPixel()
klasy BitmapData. Kompletny kod dla metody
createFisheyeMap()
zamieszczono poniżej; opatrzono go omówieniem krok po kroku wyjaśniającym jego działanie:
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;
}
Przede wszystkim wywołanie tej metody wiąże się z otrzymaniem parametru
radius
, wskazującego promień obrazu w kształcie okręgu, który ma zostać utworzony. Następnie kod tworzy obiekt BitmapData, w którym zostanie narysowany okrąg. Obiekt ten o nazwie
result
, jest następnie przekazywany z powrotem jako wartość zwracana metody. Zgodnie z tym, co pokazano w poniższym fragmencie kodu, tworzoną instancję
result
obiektu BitmapData charakteryzują szerokość i wysokość odpowiadające średnicy okręgu, bez przezroczystości (trzeci parametr równy
false
), oraz ze wstępnym wypełnieniem kolorem
0x808080
(średnio szary):
var result:BitmapData = new BitmapData(diameter,
diameter,
false,
0x808080);
Następnie w kodzie wykorzystywane są dwie pętle służące do uzyskania iteracji cyklu wykonywanego na kolejnych pikselach. Pętla zewnętrzna przechodzi przez każdą z kolumn obrazu od lewej do prawej (korzystając przy tym ze zmiennej
i
do reprezentowania położenia poziomego piksela, którego dotyczy manipulacja), podczas gdy pętla wewnętrzna przechodzi przez każdy z pikseli z bieżącej kolumny od góry do dołu (korzystając przy tym ze zmiennej
j
reprezentującej pionowe położenie bieżącego piksela). Kod dla pętli (pominięto środkowy fragment pętli) jest następujący:
for (var i:int = 0; i < diameter; i++)
{
for (var j:int = 0; j < diameter; j++)
{
...
}
}
W miarę wykonywania pętli na kolejnych pikselach obliczane są kolejne wartości (wartości koloru tego piksela na obrazie mapy). Ten proces obejmuje cztery kroki:
-
Kod oblicza odległość bieżącego piksela od środka okręgu wzdłuż osi x (
i - radius
). Wartość ta jest dzielona przez długość promienia, co umożliwia wyrażenie jej procentowo względem długości promienia, nie zaś w wartościach bezwzględnych (
(i - radius)/ radius
). Ta wartość procentowa jest zapisywana pod zmienną o nazwie
pctX
, a wartość jej równoważna, dla osi y, jest obliczana i zapisywana pod zmienną
pctY
, co ilustruje poniższy kod:
var pctX:Number = (i - radius) / radius;
var pctY:Number = (j - radius) / radius;
-
Korzystając z powszechnie znanego wzoru trygonometrycznego (twierdzenie Pitagorasa), można obliczyć odległość liniową środka okręgu od bieżącego punktu, na podstawie znanych wartości
pctX
i
pctY
. Uzyskana wartość jest zapisywana pod zmienną o nazwie
pctDistance
, zgodnie z poniższym przykładem:
var pctDistance:Number = Math.sqrt(pctX * pctX + pctY * pctY);
-
Następnie kod kontroluje, czy wartość procentowa odległości jest mniejsza od 1 (co odpowiada 100% długości promienia, lub, innymi słowy, czy piksel znajduje się w obszarze zakreślonym promieniem okręgu). Jeśli piksel przypada na obszar poza okręgiem, jest mu przypisywana obliczona wartość koloru (tę część w niniejszym wątku pominięto; opisano ją w kroku 4); w przeciwnym wypadku z pikselem nie dzieje się nic, tak że kolor pozostaje domyślny — średnio szary:
if (pctDistance < 1)
{
...
}
-
W przypadku pikseli przypadających poza okrąg wartość koloru jest obliczana wyłącznie dla piksela. Ostatecznym kolorem będzie odcień czerwonego w zakresie od czarnego (0% czerwonego) przy lewej krawędzi okręgu do jasnego (100% czerwonego) przy prawej krawędzi okręgu. Wartość koloru jest obliczana początkowo w trzech składnikach (czerwonym, niebieskim i zielonym) z osobna, zgodnie z poniższym przykładem:
red = 128 * (1 + 0.75 * pctX * pctX * pctX / (1 - pctY * pctY));
green = 0;
blue = 0;
Należy zauważyć, że tylko czerwony składnik koloru (zmienna
red
) faktycznie ma wartość. Wartości kolorów zielonego oraz niebieskiego (zmienne
green
i
blue
) są wyświetlane jedynie dla przejrzystości opisu; w rzeczywistości mogłyby być pominięte. Ponieważ celem tej metody jest utworzenie okręgu zawierającego gradient czerwieni, nie są wymagane wartości kolorów zielonego ani niebieskiego.
Po wyznaczeniu trzech różnych wartości kolorów są one łączone w pojedynczą wartość koloru w postaci liczby całkowitej za pomocą standardowego algorytmu przesuwania bitowego, zgodnie z zawartością poniższego kodu:
rgb = (red << 16 | green << 8 | blue);
Ostatecznie, po obliczeniu wartości koloru, wartość ta jest przypisywana do bieżącego piksela za pomocą metody
setPixel()
obiektu
result
BitmapData, jak poniżej:
result.setPixel(i, j, rgb);
|
|
|