Главная
К библиотеке


JAVA И WEB-АНИМАЦИЯ

Владимир КАЛАШНИКОВ

Генерация 2D-текстур Java-апплеты позволяют украсить и оживить Web-страницы. Чтобы разработать хороший Java-апплет, наряду с опытом программирования требуется и творческая жилка.

    Едва ли найдется человек, который не слышал слова Java. Что это такое? С одной стороны, это объектно-ориентированный язык программирования, который должен ни много ни мало спасти мир (по заверениям представителей фирмы Sun Microsystems Inc.). А с другой — мы имеем дело с его конкретными реализациями на платформах Windows, UNIX и иных. Можно много говорить о достоинствах и недостатках этого языка (недостатков больше), но давайте рассмотрим небольшой пример применения его возможностей на практике.

Инструментарий программиста

    Прежде всего нам понадобится компилятор; пусть это будет JVC.EXE фирмы Microsoft из пакета MS-Java SDK v2.0. Это самый быстрый компилятор, по крайней мере из тех, что я знаю. При работе потребуется также использовать справки о языке и его библиотеках. Самому мне больше всего понравились справочные файлы из пакета Visual Caffe v2.0 фирмы Symantec. Все это вместе займет на вашем “винчестере” около 8 Мб. Нет смысла ставить какой-либо пакет, работающий с Java, целиком — так вы потратите впустую мегабайты. Не стоит также пытаться писать на Java полноценные приложения. Пока такая возможность — лишь иллюзия, навеваемая фирмами — разработчиками программного обеспечения, где были созданы такие продукты, как JScript и VBScript. Выбор текстового редактора оставляю за читателем. Успех или неуспех анимированных с помощью Java Web-страниц зависит от того, есть ли в Web-браузере средства JIT-компиляции (JIT (сокращение от английского Just-In-Time) — это составная часть Java Run-Time support library; она очень сильно зависит от выбора клиентской платформы. Например, для Windows 95 поддержку Java обеспечивают JAVAPRXY.DLL, JAVART.DLL, JAVASUP.VXD, MSJAVA.DLL и некоторые другие системные библиотеки.

    Пользователи обычно не вникают в тонкости функционирования Java-машины на их компьютерах. Параметры конфигурирования Java-машины выносятся (для той же Windows 95) в Control Panel. В Windows NT JIT-компилятор потребляет столько системных ресурсов, что взаимодействие с пользователем почти останавливается (ну, это забота любящей нас Microsoft).) . Без них почти любой Java-апплет будет работать очень медленно. Но грустить не стоит, ибо почти все современные браузеры позволяют использовать JITсредства. (JIT-компиляция — это всего лишь перевод кодов виртуальной машины Java в код реального процессора. Код на выходе JIT-компилятора зачастую достаточно далек от оптимального.) Для примера скажу, что в Internet Explorer v. 3.0 и Netscape Navigator v. 4.0 (а также в их последующие версии) JIT-компилятор включен по умолчанию.

“Апплет”, или О чем речь?

    Происхождение слова апплет проследить нетрудно: это написанное кириллицей английское слово applet, в дословном переводе означающее “приложеньице”. Апплет представляет собой один или несколько файлов с расширением .class. Такие файлы — продукт работы Java-компилятора. Здесь я сделаю небольшое, с абзац, пояснение, которое люди, знакомые с программированием, смело могут пропустить. Каждый файл с расширением .class содержит набор данных (так называемых полей) и перечень действий по их обработке (эти действия в объектно-ориентированном программировании иначе называют методами). Все это в целом называется лаконично — объект. В дальнейшем я буду использовать эти термины без пояснений.(Те, кому нужны детали, могут воспользоваться прекрасным учебником Гради Буч “Объектно ориентированное проектирование с примерами применения” (М: “Конкорд”, 1992, 519 с., илл.).) Как уже, наверное, ясно из изложенного выше, апплет получает в свое распоряжение прямоугольную область экрана. Web-браузер также “сообщает” апплету о действиях пользователя (например, о перемещении курсора “мыши” в области видимости апплета, или о нажатии пользователем тех или иных клавиш и т. д.) Более детально эти механизмы будут рассмотрены далее.

    Что же могут нам дать такие возможности, собранные вместе в апплете? В контексте этой статьи можно сказать так: динамическую генерацию изображений (логотипов, заставок, или, например, баннеров для Интернета, хотя, как я заметил, все их терпеть не могут), а также воздействие на них при помощи средств ввода. Таким образом можно создавать меню, демонстрационные ролики и многое, многое другое. Взгляните на иллюстрации в конце статьи. Но… Многим может закрасться в душу сомнение: а стоит ли не глядя запускать первый попавшийся в Интернете апплет? Или, иными словами, кто решится запускать на своем компьютере все программы подряд, пусть даже маленькие или “не совсем настоящие”? Ответ таков: любые важные действия — доступ к файлам, буферу обмена, сетевым соединениям и прочие — апплету запрещены. Правда, о том, насколько такой запрет надежен, могут судить лишь разработчики браузера и виртуальной Java-машины. Иначе говоря, кто в наше время верит Microsoft?( Пока эта статья готовилась к печати, вышла книга “Атака на Интернет” (http://www.hackzone.ru), написанная Ильей Медведовским, Павлом Семьяновым и Дмитрием Леоновым. В ней описывается одна из ошибок в реализации виртуальной Java-машины Microsoft и пример ее использования. Дождались... ) 


Рис. 1. Взаимодействие апплета с Web-браузером

    Перейдем к техническим подробностям устройства апплета. Но сначала я хочу привести краткую мнемоническую схему (рис. 1), описывающую взаимодействие апплета с его окружением. Схема, изображенная на рис. 1, предназначена для того, чтобы дать лишь общее представление: в ней не отражены многие из существующих методов, например action(), lostFocus(), gotFocus() и т. д. Невозможно в рамках краткой статьи рассказать обо всем, да и не нужно, ведь есть документация, о которой я упоминал в самом начале. Однако о самых важных методах и их использовании будет рассказано на программном примере, проходящем сквозь всю статью. Итак, последовательность действий при создании апплета такова. Нужно реализовать методы апплета, отвечающие за:

  • его создание, инициализацию и уничтожение;
  • отображение созданной анимации;
  • ввод команд пользователем.

    Затем следует реализовать собственно “полезную нагрузку” апплета — анимацию.

Выбор цели, или 2D-текстура “синусная плазма”

    Согласно документации, апплет инициализируется браузером посредством вызова метода init() самого апплета, а уничтожается — по вызову метода destroy(). Запускают и останавливают апплет методами start() и stop(). Но то, как это происходит, зависит от конкретного браузера и даже от его версии. Ну а нам ничего не остается, как придерживаться принятых соглашений. И еще одна особенность — в Java встроен “сборщик мусора”. Это отнюдь не новая технология, как ее пытаются представить (реализации LISP содержали такую подсистему еще в 50-х годах), но она позволяет программировать “снизу вверх наискось”, не заботясь о ресурсах оперативной памяти. В качестве примера программирования мы используем так называемую “синусную плазму”( Жаргонный “термин”.) , “растянутую” на сектор круга. Что такое ее построение? Ничего сложного — это всего лишь функция, представляющая собой объединение синусов по координатам X и Y со специально подобранными коэффициентами (можно использовать любые периодические функции):

 Color[X][Y] = SinY[(Y+SinY[Y % 256]+SinX[(X+SinY[(Y+Delta) % 256]) % 256]+Delta) % 256], где SinY[ ]

 — табличная функция, определенная в целочисленном диапазоне 0...255 и представляющая собой значения функции sin() диапазона аргументов (0..4) x PI, приведенные к диапазону 0...127. Аналогично SinX[ ] — это отображение функции sin() дапазона аргументов (0...2) x PI к диапазону 0...255. Параметр Delta определяет циклическое (в силу характера заданных функций) изменение картинки с течением времени.

Штрихи из документации

   Включение апплета JAVA в HTML-документ выполняется специально предусмотренным тегом, например: 

<html>
<body>
<applet code=”rplasm.class”align=”middle” width=”256” height=”256”
alt=”Radial Plasma.”>
Static picture. Your browser doesn’t support Java.
<img src=”rplasm.jpg”>
</applet>
</body>
</html>

  Тег applet указывает на включение в HTML-документ Java-апплета. Web-браузер, который может обрабатывать Java-апплеты, отводит в области форматированного документа зону шириной width и высотой height пикселей и выравнивает ее относительно границ документа по параметру align. Если браузер не поддерживает Java, будет выведено сообщение, находящееся между тегами applet и /applet. В этой области можно разместить снимок с экрана при работе апплета. Тег applet имеет также много других параметров, достаточно полно представленных в документации.

Пишем апплет

    Для начала дадим несколько пояснений. Апплет будет иллюстрироваться нумерованными фрагментами (листингами). Они приведены последовательно, друг за другом, так, что, просто объединив листинги в одно целое, мы получим полный текст Java-апплета. В листинге 1 исходного текста декларируется новый класс rplasm — расширение классов Applet и Runnable. Что касается класса Runnable, то в терминах Java это называется интерфейсом. Интерфейс — абстрактный класс, который может участвовать во множественном наследовании. Такой механизм — не что иное как попытка добиться, чтобы язык был и прост, и в то же время достаточно выразителен.(Вопрос о множественном наследовании, а также о конкретном подходе, использованном в Java, достаточно подробно освещен в книге Тимоти Бадда An Introduction to Object Oriented Programming (2nd Edition; Addison Wesley Reading, Massachusetts, 1997, 452 pp). Первое издание этой книги было недавно переведено на русский.)

Листинг 1
import java.util.Random; import java.applet.Applet;
import java.awt.image.*; import java.awt.*;
public class rplasm extends Applet implements Runnable

    Программисты на Java говорят, что “класс реализует определенный интерфейс”. Зачем мы реализуем интерфейс Runnable, будет видно позднее. Ключевые слова import “указывают” компилятору на частое использование в тексте программы определенных библиотек, что позволяет применять сокращенные наименования объектов из этих библиотек (без указания имен последних). А сейчас определим необходимые данные (листинг 2).

Листинг 2
final int GC_Count=30; final int X_Size=256,Y_Size=256;
IndexColorModel PAL; byte CPal[]=new byte[256]; int XY_Size,MX,MY;
int Paint_Area[]; Thread Framer; int SinX[],SinY[]; int FlowY=0;
int Radial_X[], Radial_Y[]; Random rand; double Angle; int Numbo;

    Из соображений эффективности программирования размер рабочей области всегда равен 256 на 256 пикселей. Для нашей анимационной картинки мы воспользуемся двумястами пятьюдесятью шестью градациями серого цвета (в диапазоне 0...255: 0 — черный, 255 — белый); этого хватит, а посему декларируем объект IndexColor Model PAL. Объект Thread Framer будет использован при отображении анимации. Остальные массивы — служебные, их назначение понятно из названия. Функция из листинга 3 предназначена для описания апплета; как правило, ее никто не вызывает (разве что AppletViewer фирмы SunMicrosystems при выборе пункта меню File/About). Однако давайте корректно ее определим (листинг 3).

Листинг 3
public String getAppletInfo() {
return ”\”The Radial Ektoplasma\” v1.0 15-Nov-1998y (C) by HW”;
public void start() { Framer=new Thread(this); Framer.start(); }
public void stop() { Framer.stop(); Framer=null; }
public boolean imageUpdate(Image Img, int f, int x,
int y, int w, int h) {return true;}

    А теперь самое интересное — листинг 4.

Листинг 4
public void start() { Framer=new Thread(this); Framer.start(); }
public void stop() { Framer.stop(); Framer=null; }
public boolean imageUpdate(Image Img, int f, int x,
int y, int w, int h) {return true;}

 

Тем, кто за клавиатурой

   Для начала тем, кто уже сидит за клавиатурой, предлагаю несколько функций (листинг А), подобранных мною. Их строят методы DrawPlasma1()..DrawPlasma6(). Соответствующие иллюстрации текстур приведены на рисунках.
Листинг А

void DrawPlasma1() { int x,y,p=0;
for (y=0;y<Y_Size;y++) for (x=0;x<X_Size;x++) {
Paint_Area[p++]=SinX[(x+SinX[x]
+SinY[(x+SinY[y]+FlowY) & 0xFF]
+FlowY) & 0xFF];
}
}
void DrawPlasma2() { int x,y,p=0;
for (y=0;y<Y_Size;y++) for (x=0;x<X_Size;x++) {
Paint_Area[p++]=SinX[(x+SinX[(x+SinY[y]) & 0xFF]
+SinY[(y+SinX[(x+FlowY) & 0xFF]) & 0xFF]
+FlowY) & 0xFF];
}
}
void DrawPlasma3() { int x,y,p=0;
for (y=0;y<Y_Size;y++) for (x=0;x<X_Size;x++) {
Paint_Area[p++]=SinX[(x+SinX[(SinX[x]
+SinY[y]) & 0xFF]
+SinY[(SinY[y]+SinX[(x+FlowY) & 0xFF]) & 0xFF]
+FlowY) & 0xFF];
}
}
void DrawPlasma4() { int x,y,p=0;
for (y=0;y<Y_Size;y++) for (x=0;x<X_Size;x++) {
Paint_Area[p++]=SinX[(x+SinX[(SinX[SinX[x]]
+SinY[y]) & 0xFF]
+SinY[(y+SinX[(x+FlowY) & 0xFF]) & 0xFF]
+FlowY) & 0xFF];
}
}
void DrawPlasma5() { int x,y,p=0;
for (y=0;y<Y_Size;y++) for (x=0;x<X_Size;x++) {
Paint_Area[p++]=SinX[(x+SinX[(SinX[(SinX[x]
+SinY[y]+FlowY) & 0xFF]
+SinY[y]) & 0xFF]
+SinY[(y+SinX[(x+FlowY) & 0xFF]) & 0xFF]
+FlowY) & 0xFF];
}
}
void DrawPlasma6() { int x,y,p=0;
for (y=0;y<Y_Size;y++) for (x=0;x<X_Size;x++) {
Paint_Area[p++]=SinX[(x+SinX[(SinX[(SinX[x]
+SinY[y]+FlowY) & 0xFF]
+SinY[(SinX[x]+y) & 0xFF]) & 0xFF]
+SinY[(y+SinX[(SinX[x]+FlowY) & 0xFF]) & 0xFF]
+FlowY) & 0xFF];
}

    Метод start() вызывается браузером. Он указывает на необходимость начать отображение рабочей области апплета. Метод stop() вызывается тогда, когда надо прекратить отображение (например, окно браузера минимизировано). В этих методах нить (цепочка) выполнения создается, активируется и уничтожается. Нити передается объект с интерфейсом Runnable (в данном случае сам апплет), нить “готовит” переданный ей объект к выполнению. Оператор Framer=null — это указание “сборщику мусора” на то, что объект “нить” нам больше не нужен (это мелочь, но, все же, неплохой пример того, куда завели нас благие намерения разработчиков языка Java). А метод imageUpdate() разрешает апплету обновить его рабочую область (мы используем всю область построения, но в общем виде код данного метода может значительно усложниться).

Листинг 5
public void paint(Graphics g) {
g.drawImage(createImage(new MemoryImageSource(
X_Size,Y_Size,PAL,Paint_Area,0,X_Size)
),0,0,X_Size,Y_Size,this);
public boolean mouseEnter(Event e, int x, int y) {
IamAlive(); MX=x; MY=y; return true;}
public boolean mouseExit(Event e, int x, int y) {
showStatus(””); MX=MY=128; return true;}
public boolean mouseMove(Event e, int x, int y) {
MX=x; MY=y; return true;}
public boolean mouseDown(Event e, int x, int y) {
Numbo++; IamAlive(); RecalculateRadials(); return true;}

    Еще немного вспомогательного текста (листинг 5), а затем мы перейдем к самому алгоритму построения. Метод paint() конвертирует нашу матрицу построения в изображение на экране, а методы mouseEnter(), mouseExit(), mouseMove(), mouseDown() вызываются при соответствующих событиях. Надо сказать, по мнению разработчиков языка Java, вышеописанная реакция на события несколько устарела, и в версии языка 1.2 рекомендуется использовать другие методы (хотя кто они вообще такие, чтобы решать за нас?). Однако Java настолько противоречив и непоследователен, что в данном случае большого греха не будет — напишем так, как удобно. В вышеприведенном фрагменте текста анимация будет следовать за курсором мыши (а если курсор находится за пределами области построения, изображение будет центрироваться). При нажатии любой клавиши мыши будет изменяться масштаб построения. Теперь мы подошли к самому главному, к инициализации и запуску нашего апплета — это листинг 6 исходного текста.

Листинг 6
public void init() { rand=new Random();
for (int i=0;i<=127;i++) CPal[i]=(byte)(i*2);
for (int i=128;i<=255;i++) CPal[i]=(byte)((255-i)*2);
PAL=new IndexColorModel(8,256,CPal,CPal,CPal);
XY_Size=X_Size*Y_Size; Paint_Area=new int[XY_Size];
Radial_X=new int[XY_Size*4]; Radial_Y=new int[XY_Size*4];
SinX=new int[X_Size]; SinY=new int[Y_Size];
for (int i=0;i<X_Size;i++)
SinX[i]=(int)( (Math.sin(Math.PI*i*4/X_Size)+1) *63);
for (int i=0;i<Y_Size;i++)
SinY[i]=(int)( (Math.sin(Math.PI*i*2/Y_Size)+1) *127);
MX=MY=128; RecalculateAngle(); Numbo=1; RecalculateRadials();

    В методе init() мы создаем рабочую черно-белую палитру в цветовой модели RGB, а также рабочие массивы области построения. (Размерность массива задается при его создании в операторе new, а декларация массива состоит лишь в указании ссылки на него.( Напомним, что в Java статических объектов нет; вся память берется из “кучи” и возвращается в нее, правда, в конкретных реализациях рассматриваются разные области памяти внутри “кучи”, но дальше P-кода Java это не распространяется.) Создаем также таблицы рассмотренных выше функций SinX и SinY. Только табличные вычисления позволяют достичь на Java скольконибудь приемлемой скорости выполнения. Метод RecalculateRadials() будет рассмотрен чуть позже. Рассмотрим следующий фрагмент (листинг_7).

Листинг 7
public void run() {
int Loop; while (true) {
for (Loop=0;Loop<GC_Count;Loop++) { FlowY=(FlowY+4) & 0xFF;
DrawPlasma(); paint(getGraphics());} System.gc();}}

    Метод run() является частью интерфейса Runnable и готовится к выполнению объектом Thread Framer (приоритет выполнения может быть указан при создании объекта Thread). При этом мы непрерывно отображаем нашу анимацию. Эксперименты показали, что Java настолько медлителен даже с JIT-компилятором, что на машинах от Pentium 120 до Pentium II 300 никаких “задержек” не нужно. А вот что нужно, так это принудительный сбор “мусора” через несколько кадров анимации (в нашем случае 30). Делают это, вызвав System.gc(). Тем не менее, возможен неконтролируемый рост файла “подкачки” (это зависит от версии браузера). Я уже обещал, что мы будем “натягивать” нашу “плазму” на сектор круга. Для этого необходимо рассчитать таблицу перевода декартовых координат построения в полярные. Именно это и выполняется фрагментом, приведенным в листинге_8.

Листинг 8
void RecalculateAngle() {
Angle=(double)(Math.abs(rand.nextInt()) % 20)/2+0.5; // SetUp angler
}
void RecalculateRadials() { // SetUp radials
double aa=(X_Size)*Angle/2/Math.PI;
for (int y=0;y<Y_Size;y++) for (int x=0;x<X_Size;x++) { int p;
/* int r=Math.min((int)Math.sqrt(x*x+y*y),Y_Size); // Radial range */
/* int r=(int)Math.sqrt(x*x+y*y) % Y_Size; // Over range */
int r=(int)Math.sqrt(x*x+y*y)*Numbo % Y_Size; // Many plasmas
double a=(x==0)?Math.PI/2:Math.atan((double)y/(double)x);
p=(Y_Size+y)*2*X_Size+(X_Size+x); Radial_Y[p]=r;
Radial_X[p]=(int)(a*aa) % X_Size; // 90 -> 0 [4]
p=(Y_Size-y)*2*X_Size+(X_Size+x); Radial_Y[p]=r;
Radial_X[p]=(int)((2*Math.PI-a)*aa) % X_Size; // 270 -> 360 [1]
p=(Y_Size-y)*2*X_Size+(X_Size-x); Radial_Y[p]=r; // 270 -> 180 [2]
Radial_X[p]=(int)((Math.PI+a)*aa) % X_Size;
p=(Y_Size+y)*2*X_Size+(X_Size-x); Radial_Y[p]=r;
Radial_X[p]=(int)((Math.PI-a)*aa) % X_Size; // 90 -> 180 [3]
}

    Переменной Angle определяется количество секторов в круге (сектор равновелик кругу, если это сектор с углом охвата в 360®), а переменной Numbo — радиальная высота сектора (масштаб). Можно предположить, что приведенный выше фрагмент текста будет выполняться довольно долго. Что делать? Можно заполнять таблицу от центра к краям (сразу в две стороны) — это тем более просто, что достаточно вычислять лишь один квадрант таблицы, остальные квадранты получаются из исходного с помощью аффинных преобразований. Такой трюк позволит добиться эффекта “раздвигания шторок” перед следующими кадрами анимации. Это и смотрится красиво, да еще и проблема задержки вычислений решается (вспомним: нажав на клавишу мыши, мы меняем масштаб отображения). (В исходном тексте метода Radials() можно “раскомментировать” некоторые места. К чему это приведет? — Оставляю вам возможность поэкспериментировать.) Ну и, наконец, код, завершающий построение — листинг 9 исходного текста.

Листинг 9
void DrawPlasma() { int xx,yy,x,y,p=0,pp;
pp=(~MX & 0xFF)+((~MY & 0xFF) << 9);
for (y=0;y<Y_Size;y++) { for (x=0;x<X_Size;x++) {
xx=Radial_X[pp]; yy=Radial_Y[pp++];
Paint_Area[p++]=SinY[(yy+SinY[yy & 0xFF]
+SinX[(xx+SinY[(yy+FlowY) & 0xFF]) & 0xFF]
+FlowY) & 0xFF];
} pp+=0x100; }
}
void IamAlive() {
showStatus(”<Zoom= ”+Integer.toString(Numbo)+
”; Radial=”+Double.toString(Angle)+
”> Radial ektoplasma is here... Call Ghost busters!”);
}

    Вот так, выполняя одни лишь подстановки из таблиц и операции сложения, мы строим в реальном режиме времени довольно сложный график функции, отражающей яркость точек. Методом IamAlive() параметры построения выводятся в строку состояния. Следует отметить, что, соединив все приведенные в тексте фрагменты программы, мы получим целостный компилируемый рабочий код апплета. Что же должно выйти? На рис. 2 представлены построения для одного, а на рис. 3 — для 10 секторов на круге. Кроме того, на втором рисунке масштаб равен трем (3 сектора на радиус). Заменив функцию построения текстуры, можно получить, например, картинку, приведенную на рис. 4. Вы не задавались вопросом, как выглядит оригинальная текстура (в декартовой плоскости XOY)? Тогда взгляните на рис. 5. Исходные тексты апплета, а также сам апплет можно выкачать по адресу http://www.cp.comizdat.com/program/java/rplasm.zip 


Рис. 2.
«Синусная плазма» для 1 сектора


Рис. 3.
«Синусная плазма» для 10 секторов


Рис. 4.
Достаточно заменить функцию построения текстуры…


Рис. 5.
Оригинальная текстура в декартовой плоскости

Последнее напутствие

    Рассмотренный в этой статье пример легко приспособить к любым вычислениям и использовать как “скелет” для построения других апплетов. Хочу только предостеречь от попытки встроить более одного апплета в страницу. Ресурсы нынешней вычислительной техники этого не позволяют, и лучше не искушать судьбу. Скажу еще одну не вполне очевидную вещь: наряду с работой анимированного ролика (созданного в нашем апплете) можно пользоваться всеми остальными возможностями иерархии applet, подклассом которой является наша программа. Например, мы можем применять стандартные объекты пользовательского интерфейса “поверх” анимации. Выглядит это очень эффектно. (К сожалению, пример уже выходит за рамки этой статьи.)

Владимир КАЛАШНИКОВ,
 технический консультант НПП “Контур”.
Область интересов: безопасность информационных систем;
символьная обработка и искусственные языки,
трансляторы. FidoNet: 2:461/133.69;
e-mail: hw@p69.f133.n461.z2.fidonet.org;
rus47@public.kharkov.ua;
тел: (057-47) 3-50-01

Источник: http://www.cp.comizdat.com/

Copyright © 1999-2000гг. "Internet Zone" & Nik Romanov. nikspase@mail.ru, mailto:nikspase@hotmail.ru  http://www.izone.com.ua/
Hosted by uCoz