Пример растрового изображения: анимация вращающейся луныFlash Player 9 и более поздних версий, Adobe AIR 1.0 и более поздних версий В примере с анимацией вращающейся луны показаны техники работы с объектами Bitmap и данными растровых изображений (объектами BitmapData). В этом примере мы создадим анимированное изображение вращающейся сферической луны, взяв в качестве исходного изображения плоскую фотографию лунной поверхности. Техники, демонстрируемые в примере, перечислены ниже.
Получить файлы приложения для этого примера можно на странице www.adobe.com/go/learn_programmingAS3samples_flash_ru. Файлы приложений анимированной вращающейся луны можно найти в папке 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"));
В первой строке объявляется экземпляр Loader с именем imageLoader. Третья строка фактически начинает процесс загрузки, вызывая метод load() объекта Loader, передающий экземпляр URLRequest с URL-адресом загружаемой картинки. Во второй строке задается прослушиватель событий, который будет вызван после полной загрузки изображения. Обратите внимание, что метод addEventListener() вызывается не в самом экземпляре Loader, а, наоборот, в свойстве contentLoaderInfo объекта Loader. Сам по себе экземпляр 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() содержится свойство bitmapData загружаемого экземпляра Bitmap (event.target.content.bitmapData), сохраняемое в переменной экземпляра под названием textureMap, которая используется для создания анимации вращения луны. Описание см. далее. Создание анимации путем копирования пикселовСуть анимации в воспроизведении движения или трансформации, чего можно добиться, изменяя изображение во времени. В данном примере наша цель — воспроизвести сферическую луну, которая вращается вокруг вертикальной оси. Тем не менее, в целях анимации в данном примере можно пожертвовать точностью сферического искажения. Рассмотрим изображение, которое загружается и используется в качестве источника данных изображения луны. Как видите, это не одна и не несколько сфер, а лишь прямоугольная фотография лунной поверхности. Так как фотография сделана точно на экваторе луны, части изображения в верхней и нижней части растянуты и деформированы. Для устранения этого искажения и придания «сферичности» мы воспользуемся фильтром замещения текстуры, как будет показано позднее. Однако, поскольку исходная фотография имеет прямоугольную форму, для создания иллюзии вращения сферы нужно написать код, который будет сдвигать поверхность луны в горизонтальном направлении. Обратите внимание, что изображение состоит из двух расположенных рядом копий фотографии лунной поверхности. Эта фотография является источником данных изображения, которые многократно копируются для создания иллюзии вращения. Расположение двух копий фотографии друг рядом с другом удобнее для создания эффекта непрерывного скольжения. Рассмотрим процесс анимации шаг за шагом. В этом процессе участвуют два отдельных объекта ActionScript. Во-первых, у нас есть загруженное исходное изображение, представленное в коде экземпляром BitmapData с именем textureMap. Как описано выше, textureMap заполняется данными, как только загрузится внешнее изображение. Для этого используется следующий код: textureMap = event.target.content.bitmapData; Содержимое textureMap представляет собой прямоугольное изображение луны. Кроме того, для создания анимации вращения в коде используется экземпляр Bitmap с именем sphere, который является экранным объектом, выводящим изображение луны на экран. Как и 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. Иными словами, содержимое BitmapData объекта sphere является частью объекта textureMap, которая выделена на рисунке. Не забывайте, что это лишь начальное состояние объекта sphere — первое изображение, которое копируется в объект sphere. Когда исходное изображение загружено, а объект sphere создан, остается последняя задача — создать анимацию. Она решается методом imageLoadComplete(). Анимацией управляет экземпляр Timer с именем rotationTimer, который создается и запускается с помощью следующего кода: var rotationTimer:Timer = new Timer(15); rotationTimer.addEventListener(TimerEvent.TIMER, rotateMoon); rotationTimer.start(); Сначала создается экземпляр Timer с именем rotationTimer. Параметр, передаваемый конструктору Timer(), указывает, что экземпляр rotationTimer должен вызывать событие timer каждые 15 миллисекунд. Затем вызывается метод addEventListener() и указывает, что при выполнении события timer (TimerEvent.TIMER) вызывается метод rotateMoon(). Наконец, таймер запускается через вызов метода start(). Из-за способа определения rotationTimer проигрыватель Flash Player вызывает метод rotateMoon() в классе MoonSphere примерно каждые 15 миллисекунд, и именно таким образом осуществляется анимация луны. Исходный код метода 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), маску можно применить напрямую к экземпляру MoonSphere с помощью унаследованного свойства mask. ![]() Для создания реалистичного эффекта вращающейся сферы недостаточно просто скрыть какие-то области фотографии под круглой маской. Из-за способа фотосъемки изображение непропорционально: верхняя и нижняя части искажены гораздо сильнее, чем экваториальная область. Чтобы придать луне объемный вид, понадобится фильтр замещения текстуры. Фильтр замещения текстуры относится к тому типу фильтров, который используется для искажения изображения. В данном случае мы «исказим» фотографию луны, чтобы придать ей реалистичность. Для этого оставим середину как есть, а верхнюю и нижнюю часть сожмем по горизонтали. Так как фильтр применяется к квадратной области фотографии, сжатие верхней и нижней частей и сохранение середины в прежнем виде превратит квадрат в круг. Побочным эффектом анимации искаженного изображения является иллюзия, что пикселы в середине движутся быстрее, чем в верхней и нижней части. Это создает ощущение, что круг является объемным объектом (сферой). Ниже показан код для создания фильтра замещения текстуры 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]; Итоговое изображение (с применением маски и фильтра замещения текстуры) показано ниже. ![]() С каждым циклом анимации вращающейся луны содержимое сферы BitmapData заменяется новым снимком исходных данных изображения. Тем не менее, каждый раз применять фильтр заново необязательно, так как фильтр применяется к экземпляру Bitmap (экранному объекту), а не к данным растрового изображения (информации пикселов без сжатия). Помните, что экземпляр Bitmap — это не сами данные растрового изображения, а экранный объект, который выводит данные растрового изображения на экран. Можно провести аналогию: экземпляр Bitmap — это проектор, с помощью которого мы выводим слайды на экран, а объект BitmapData — это собственно слайд, который можно вывести на экран с помощью проектора. Фильтр можно применить напрямую к объекту BitmapData (пользуясь той же аналогией, подрисовать что-нибудь прямо на слайде, чтобы изменить картинку). Фильтр также можно применить к любому экранному объекту, включая экземпляр Bitmap — это можно сравнить с размещением фильтра перед линзой проектора для искажения выводимого на экран изображения (при этом исходный слайд не меняется). Доступ к данным без сжатия осуществляется через свойство bitmapData экземпляра Bitmap, поэтому фильтр можно применить и напрямую к несжатым данным растрового изображения. Тем не менее, лучше применять фильтр к экранному объекту Bitmap, а не к данным растрового изображения. Подробную информацию об использовании фильтра замещения текстуры в языке ActionScript см. в разделе Фильтрация экранных объектов. Создание растрового изображения путем установки значений пикселовВажным свойством фильтра замещения текстуры является то, что он состоит из двух изображений. Одно из них — исходное — это изображение, которое изменяется в результате наложения фильтра. В данном примере исходным изображением является экземпляр Bitmap с именем sphere. Второе изображение, используемое фильтром — это изображение текстуры. Текстура не отображается на экране. Вместо этого цвет каждого из ее пикселов используется в качестве входа функции замещения, т.е. цвет пиксела с некоторыми координатами x, y в изображении текстуры определяет, какое смещение (физическое изменение положения) применяется к пикселу с такими же координатами х, у в исходном изображении. Следовательно, чтобы использовать фильтр замещения текстуры для создания сферического эффекта, потребуется подходящее изображение текстуры: картинка с серым фоном и кругом, залитым одноцветным (красным) горизонтальным градиентом с переходом от темного к светлому, как показано ниже. ![]() Так как в примере используется только одно изображение текстуры, оно создается только один раз — в методе imageLoadComplete() (иными словами, после окончания загрузки внешнего изображения). Изображение текстуры с именем fisheyeLens создается в результате вызова метода createFisheyeMap() класса MoonSphere: var fisheyeLens:BitmapData = createFisheyeMap(radius); Внутри метода createFisheyeMap() изображение текстуры фактически отрисовывается по пикселам с помощью метода setPixel() класса BitmapData. Ниже приведен полный код метода 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 создается с шириной и высотой, равными диаметру круга, является непрозрачным (значение третьего параметра — 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++)
{
...
}
}
По мере прохождения циклов через пикселы вычисляется значение каждого пиксела (значение цвета этого пиксела в изображении текстуры). Этот процесс состоит из четырех шагов.
|
|