|
||||
Язык Java и его возможностиОкончание. Начало см. в № 6, 7/2008 Часть III. Динамические изображенияВ предыдущих публикациях мы уже неоднократно говорили, что главными достоинствами программ-аплетов следует назвать их интерактивность и динамичность. Сегодня мы рассмотрим подробнее, как именно создаются на Java динамически меняющиеся изображения. Известно, что создание любой
анимационной картинки опирается на средства
статической графики и эффективный механизм
замены отдельных кадров. Как читатели уже, наверное, догадались, разделение свойств интерактивности и динамичности (в терминах данной публикации это части II и III) удается осуществить благодаря относительной независимости средств их реализации. Именно поэтому приводимые ниже примеры сознательно лишены интерактивности: общепринятая в педагогике точка зрения советует при первоначальном изучении явления по возможности сконцентрироваться на его наиболее характерных чертах. Зато, освоив по отдельности интерактивность и динамичность, читатели смогут легко объединить 1 все преимущества этих мощных технологий и создать на основе Java-аплетов учебные материалы нового поколения, возможности которых существенно выходят за рамки традиционных педагогических средств. 1. Принцип создания динамического изображенияОбщая идея получения изменяющихся (движущихся) изображений довольно проста и интуитивно понятна: динамический процесс разбивают на отдельные фазы (кадры), которые в ходе демонстрации сменяются с необходимой скоростью. Сразу заметим, что представление непрерывного изменения в виде последовательности статических кадров есть не что иное, как дискретизация, поэтому такая технология прекрасно подходит для компьютера. Конкретные способы получения кадров и их демонстрации весьма разнообразны. Например, не так давно, когда съемка кинофильмов производилась механическими камерами на специальную светочувствительную пленку, большое внимание уделялось технике транспортировки киноленты вдоль объектива. Сейчас, когда многие люди уже не встречались с подобным оборудованием, стоит напомнить, что движение киноленты реализовывалось весьма специфическим неравномерным способом: быстрая протяжка на один кадр 2 сменялась остановкой для фиксации кадра на пленке (или его демонстрации). Помимо автоматически записанного изображения, зафиксировавшего некоторую натурную съемку, в “докомпьютерном” кинематографе кадры создавались и вручную — художники-мультипликаторы мастерски рисовали различные фазы движения своих героев, а затем оператор снимал их рисунки в режиме специальной покадровой съемки. Демонстрация художественных и мультипликационных фильмов происходила совершенно одинаково. Многочисленные эксперименты показали, что иллюзия непрерывного движения возникает при смене как минимум 10–12 кадров за секунду. В любительских киноаппаратах обычно применялась скорость 16 кадров/сек., а в профессиональных — 24. В телевидении используются совсем другие технологические принципы создания движущихся изображений, но и там речь идет о кратковременной демонстрации на экране статических кадров. Частота смены кадров составляет 25 или 30 кадров, что очевидным образом связано с частотой питающей электросети, которая принята в тех или иных странах. Переход к компьютерному видео не содержал принципиальных барьеров, но инженерам пришлось изрядно потрудиться, чтобы решить целый ряд сложных технологических проблем: обеспечить достаточное быстродействие видеосистемы, разместить большие объемы видеоданных на машинных носителях (увеличение объема последних, сжатие) и сконструировать каналы, способные обеспечить передачу этих данных без задержек. Типовое компьютерное оборудование, которое в настоящее время широко предлагается для домашнего использования в магазинах, решает все перечисленные задачи на вполне достаточном уровне. В результате просмотр видео на компьютере становится одним из любимых направлений его использования. Не менее важным обстоятельством являются также широчайшие возможности коррекции и редактирования компьютерной видеоинформации. В результате при профессиональном уровне работы разница между кадрами, реально отснятыми видеокамерой и откорректированными на компьютере, практически незаметна. Все больший реализм приобретает и компьютерная анимация. Таким образом, мы видели, что для создания на экране дисплея изменяющегося изображения очень важно иметь механизм быстрой смены статических картинок. Посмотрим, как обстоит с этим дело в языке Java. 2. Потоки как средство организации динамической графики в JavaЧто такое потоки Очевидно, что для получения динамических изображений, которые будут воспроизводиться с одинаковым эффектом на разных компьютерах, необходимы специальные средства, “привязанные” не к скоростным характеристикам аппаратуры, а к реальному времени. Иными словами, если мы хотим реализовать с помощью языка универсальную не зависящую от характеристик компьютера картинку, мы должны в операторах этого языка использовать не тактовую частоту или аналогичные технические величины, а обычные секунды и их доли. В Java имеется специальный механизм, позволяющий организовать функционирование программы в реальном времени, — это поток. Хочется подчеркнуть, что потоки, хотя и служат прекрасным средством для организации динамических изображений, созданы для гораздо более общих целей. Рассмотрим этот аспект немного подробнее. Большинство читателей хорошо понимают, что такое многозадачность, ибо современные операционные системы существенным образом приспособлены к одновременному функционированию нескольких приложений: аудиопроигрыватель, менеджер закачек из Интернета, текстовой редактор, калькулятор и т.д. плюс сама операционная система и ее внешняя оболочка для манипуляции файлами. Но и внутри каждого крупного приложения можно также распределять работу между заданиями, которые выполнять параллельно, например, в редакторе печатать документ, вести в нем поиск и производить форматирование. Описанный способ исполнения заданий настолько важен, что его вполне можно увидеть стандартными средствами Windows. Щелкните правой кнопкой мыши по панели задач и в появившемся меню выберите пункт “Диспетчер задач”; в результате вы увидите картину, похожую на ту, что приведена на рис. 1. Рис. 1. Диспетчер задач Windows В окне Диспетчера отображаются как работающие приложения, так и их процессы (а также процессы, запущенные в результате работы самой ОС). Наибольший интерес представляет собой вкладка “Процессы”, которая отражает десятки протекающих в системе процессов 3. Мы воочию видим, насколько важную роль играет многопоточность в современном компьютере. Но разделение задачи на параллельно исполняемые части характерно не только для программного обеспечения, но и для современной аппаратной части. Достаточно вспомнить о сопроцессорах и контроллерах, а также о многоядерных процессорах, которые на глазах превращаются из экзотики в норму. Бытует мнение (возможно, “подогретое” агрессивной рекламой многоядерных процессоров), что многопоточные программы работают быстрее. На самом деле можно смело утверждать обратное. Действительно, для переключения между задачами требуется дополнительное время, а если бы процессор, “не отвлекаясь”, выполнял задачи последовательно, то он завершил бы их быстрее 4. Видимо, преимущества надо искать в чем-то другом. Первый тип приложений, который выигрывает от поддержки многопоточности, предназначен для задач, где действительно требуется выполнять несколько действий одновременно, например, когда сервер обслуживает запросы нескольких клиентов. Кстати, принципиально важно, чтобы запросы были независимыми, иначе они будут мешать выполнению друг друга. Рис. 2. Основные принципы функционрования потока Следующий пример — активные приложения, где необходимо одновременно опрашивать клавиатуру и другие устройства ввода, чтобы реагировать на действия пользователя, и в то же время рассчитывать и перерисовывать изменяющееся состояние экрана. Еще одно преимущество проистекает из того, что компьютер состоит не только из одного или нескольких процессоров. Вычислительное устройство — лишь один из ресурсов, необходимых для выполнения задач. Всегда есть оперативная память, дисковая подсистема, сетевые подключения, периферия и т.д. Здесь существенно, что все перечисленные устройства работают медленнее (а многие — существенно медленнее), чем процессор и, кроме того, имеют самостоятельные элементы управления (контроллеры). В результате задачи обмена данными требуют совсем незначительного участия центрального процессора, а значит, он легко справится с параллельным обслуживанием нескольких задач. Напротив, когда задачи в основном загружают процессор (например, математические расчеты), то их одновременное исполнение займет в лучшем случае столько же времени, что и последовательное, а то и больше. Характерной чертой Java является то, что в ней изначально предусмотрена поддержка многопоточного программирования (multithread programming). Далеко не каждый язык может этим похвастаться, например, в Паскале или Бейсике ничего подобного просто нет. Примечание. При переводе терминов Java на русский язык имеется некоторая специфическая проблема. В Java существует два разных термина — thread и stream, первый из которых фактически является потоком команд, а второй — потоком данных. Тем не менее оба этих термина обычно переводятся как “поток”, что может приводить к смешению этих довольно разных понятий. В данной статье речь везде идет только о потоках типа thread. Полная модель обработки потоков в Java достаточно сложна. Язык позволяет описывать не просто изолированные, но взаимодействующие потоки, причем одни потоки способны управлять другими, они могут иметь разный приоритет, синхронизироваться и т.д. В данной публикации мы рассмотрим только основы работы с потоками, которых будет достаточно для создания динамической графики. Но даже в рамках этой задачи удастся посмотреть подлинно многопоточную реализацию (см. последний пример). Новый механизм порождает и принципиально новую проблему — взаимные блокировки (deadlock), тем более что Java не имеет встроенных средств для определения такой ситуации. Суть проблемы в том, что при неудачном стечении обстоятельств может возникнуть состояние, в котором один процесс ожидает завершения другого и наоборот. Обычно эту проблему образно представляют в виде аналогии, когда два барана встретились на узком мосту и не уступают друг другу дорогу 5. Ситуация осложняется тем, что некоторые принципы, заложенные в первые реализации Java-машины, сейчас объявлены крайне неудачными, например, методы “такие, как thread.stop и thread.resume, осуждены, поскольку они с самого начала были плохой идеей и по-настоящему опасны” [1]. Еще более устрашающе выглядит оценка метода destroy “(который никогда не был реализован), который вы не должны вызывать, если можно этого избежать” [2]. Как использовать поток для динамической графики Рассмотрим теперь, как можно использовать потоковый механизм для создания в окне аплета динамически меняющегося изображения. Обратимся к рис. 2, на котором изображены основные принципы функционирования потока. Подчеркнем, что данная схема составлена на основе систематического описания в классической книге [3]; особенности версии Java 2 будут рассмотрены по ходу изложения примеров. Любой аплет имеет некоторый набор стандартных процедур, каждая из которых играет вполне определенную роль и вызывается Java-машиной в определенное время. Названия наиболее важных процедур (init, start и т.д.) выписаны на рис. 2; дополнительная информация о них приведена в табл. 1. Особую роль играет процедура Две оставшиеся процедуры не имеют
непосредственного отношения к потокам: Примечание. Если окно аплета велико, а реально обновляется лишь небольшая его часть, при вызове метода перерисовки имеет смысл указывать координаты обновляемого прямоугольника — остальная (бо'льшая) часть рисунка при этом сохраняется. Отметим, что сам аплет также представляет собой некоторый базовый поток, так что создаваемый нами для нужд мультипликации поток будет уже вторым; он является дочерним, т.е. основной поток аплета управляет им и по окончании работы обеспечивает его уничтожение. Как уже отмечалось выше, заложенная в Java 1 идеология потоков не во всем себя оправдала, так что реальные программы для потоков не столь логичны, как описанная выше картина. Приведенные в данной публикации примеры за образец принимают официальные рекомендации фирмы Sun [4]. Кроме того, если потоки работают кратковременно и по окончании цикла работы завершаются естественным образом, то можно обойтись без дополнительных мер по их остановке. Важную роль в организации потока для
вывода меняющихся изображений играет метод В языке Java предусмотрено два механизма
создания потоков: реализация интерфейса Итак, типичное решение задачи о создании потока для динамического изображения выглядит следующим образом. · Подключить интерфейс · В методе · В методе · Если цикл воспроизведения картинки
бесконечен, в методе 3. Примеры динамических изображенийПерейдем теперь к рассмотрению примеров аплетов, создающих на экране динамические изображения. Они разнообразны по реализации, так что автор надеется, что после чтения статьи читатели сумеют выбрать “стартовую точку” для реализации своего собственного аплета. Пример 1. Реализация простейшего движения Начнем с простейшего движущегося изображения: опишем на языке Java небольшую тележку, которая перемещается в горизонтальном направлении. В качестве простейшего алгоритма поведения предлагается модельное движение “взад-вперед” между двумя граничными точками. Возможный вид аплета в некоторый фиксированный момент времени изображен на рис. 3. Рис. 3. Реализация простейшего движения Обратимся теперь к листингу 1, показывающему, как выглядит решение нашей задачи. Пропустив стандартные начальные
строчки (об их расшифровке мы подробно говорили в
части I публикации), отметим только указание
после слова Далее следует описание тех переменных,
которые являются “связующими” для
согласованной работы всех методов: поток Примечание. Модификатор В методе Наиболее интересным является метод
При входе в метод стоят две на первый
взгляд очень странные строки: в переменной Далее вычисляется новая координата
тележки. Прежде всего После этого расположена необычайно
важная для создания изображения строка —
вызывается метод Примечание. Время 20 мсек. было выбрано экспериментально. Оно соответствует скорости демонстрации 1000/20 = 50 кадров/сек., что надежно обеспечивает плавность движения тележки. Назначение следующего метода Наконец, последний метод с весьма
очевидным текстом —
Пример 2. Индикация времени Изменения, происходящие на экране, не обязательно должны быть чисто графическими. В данном примере продемонстрировано, как образовать поток, который будет отображать на экране строку с текущим временем. Подчеркнем, что такой поток можно добавлять к любому аплету, в котором вы хотите индицировать время. Рис. 4. Индикация времени Текст программы аплета приведен в листинге 2. Сравнение листингов 1 и 2 показывает, что значительная их часть совпадает. Причина очевидна — организация потока в новой программе точно такая же, как и в предыдущей. Примечание. Если вы решите взять
листинг 1 за основу и получить листинг 2 путем
внесения в него изменений, не забудьте в
начальной части программы добавить строку,
импортирующую библиотеку Рассмотрим теперь, как в аплете
формируются показания часов, для чего разберем
устройство метода Примечание. Казалось бы,
заявленные методы Итак, займемся выделением информации о
времени. Для этого, применив метод Остается вывести ее в окно аплета, что
обеспечивает метод Пример 3. Как сделать мультфильм Изучив предыдущие примеры, наблюдательный читатель может заметить, что в них динамическое изображение формировалось средствами языка Java. Конечно, это тоже не так уж мало, поскольку позволяет создать строящиеся в реальном времени диаграммы или графики, всевозможные прогресс-индикаторы, динамические схемы, плавный переход от одной картинки к другой, бегущую строку, появляющиеся и исчезающие указатели и многое другое. Тем не менее определенные ограничения на “оживляемые” картинки это все-таки накладывает: попробуйте, например, операторами Java нарисовать лошадь или котенка! Иными словами, хочется иметь возможность использовать картинки, созданные вне среды Java, например, в графическом редакторе. Решению этой задачи и посвящен пример 3. Общая идея состоит в чтении
графических файлов, в которых нарисованы
отдельные кадры будущего мультфильма, в
специальные объекты типа Image, предназначенные
для “внеэкранного” хранения изображений в
оперативной памяти и их последовательной
демонстрации. Большую помощь при написании
алгоритма может оказать “массив” из объектов
типа Предположим, что кадры для создаваемого фильма подготовлены в файлах f1.gif – f12.gif. Попутно обратим внимание на то, что Java ориентируется на принятые в Интернете графические форматы, так что .bmp здесь не подойдет. Алгоритм создания такого 12-кадрового мультфильма записан в листинге 3. Все имеющиеся в программе описания нам
уже знакомы, кроме объекта типа Перейдем к рассмотрению метода Метод Примечание. Выбранное в условии значение 70 получено путем приблизительного деления 400 (горизонтальный размер окна аплета) на 6 (смещение объекта за один кадр — обоснование последнего значения будет дано ниже). В данном аплете метод Остается разобраться с текстом метода
Если не считать отдельных деталей вроде величины смещения и цвета фона, программа в листинге 3 весьма универсальна и годится для любого мультфильма. Автор при тестировании воспользовался картинками, подготовленными в свое время к одной из своих разработок [5]. Там в одном из первых демонстрационных примеров использовались картинки катящегося пятнистого мяча (пятна, как известно, позволяют легко заметить вращение). Изначально мяч был нарисован в редакторе CorelDraw и скопирован 11 раз с поворотами, кратными 360/12 = 30 градусам. В итоге получились 12 фаз (циклически повторяющегося) движения — они приведены на рис. 5. Рис. 5. Кадры движения катящегося мяча Теперь можно, наконец, рассчитать, на сколько пикселей в горизонтальном направлении должен сместиться мяч при переходе от одной картинки к другой (т.е. при повороте на 30 градусов): 2R/12 R/2. Для применяемых в аплете картинок получалось значение 7 пикселей, экспериментально картинка казалась оптимальной при шести. Примечание. Чем меньше смещение, тем “плавнее” катится мяч. Но слишком маленькой эту величину тоже делать нельзя, поскольку мяч начнет “проскальзывать”: вращение будет казаться быстрее, чем горизонтальное перемещение. На рис. 6 приведен общий вид итогового аплета. Чувствуется, что статическая картинка на бумаге много теряет по сравнению с реальным динамическим изображением! Рис. 6. Аплет, демонстрирующий простейший мультфильм Описанный в данном примере способ создания небольших мультфильмов, конечно, требует наличия некоторых художественных способностей, чтобы изобразить нужные фазы движения (к слову, простейшие динамические картинки учебного назначения многие способны сделать в хорошем графическом редакторе и не будучи живописцами!). Но можно поискать и готовые серии рисунков в Интернете. В частности, большой известностью пользуется нарисованный в 1989 году художником по имени Кенджи Готох (Kenji Gotoh) маленький симпатичный котенок Неко 6. Талантливо сделанные рисунки позволяют из относительно небольшого числа картинок (см. [6, 7]) получить весьма занимательные “мультики”, в которых Неко бегает, чешет за ушком и ложится спать. Как это выглядит, можно непосредственно понаблюдать на сайте [8]. Подчеркнем, что если вы умеете читать по-английски, то приведенные выше ссылки [6] и [7], представляющие собой самоучители по Java, объяснят вам некоторые дополнительные приемы анимирования изображений. Пример 4. Многопотоковый аплет Во всех рассмотренных выше примерах мы имели дело с единственным потоком, создававшим динамическое изображение. (Не стоит, правда, забывать, что запрограммированный нами поток был на самом деле вторым, поскольку сам аплет также является потоком.) В данном примере мы рассмотрим создание изображения с помощью нескольких параллельно работающих потоков. В качестве демонстрационной задачи рассмотрим ход параллельного пучка лучей в собирающей линзе — рис. 7. Из школьного курса физики известно, что, проходя через собирающую линзу, лучи пересекаются в характерной точке, которая называется фокусом. В нашем аплете параллельный пучок моделируется пятью лучами (считая проходящий вдоль оси x), но изменить это число не составляет никакого труда. Помимо общей схемы движения лучей, которую мы планируем увидеть по завершении работы аплета, на рис. 7 указаны в пикселях экрана характерные размеры изображения. Рис. 7. Схема, демонстрирующая ход лучей в собирающей линии
Несколько слов о том, откуда берется формула y = y0 * (1 – x/F), описывающая ход луча после прохождения линзы. Из математики известно, что уравнение любой не вертикальной прямой представляется в виде y = kx + b, которое справедливо в любой ее точке. Воспользуемся последним обстоятельством и запишем данное уравнение для двух характерных точек (0, y0) и (F, 0). Благодаря нулевым координатам полученная система из двух уравнений разрешается моментально; автор надеется, что среднестатистический школьник еще в состоянии это сделать. Примечание. По мнению автора, при решении задач по информатике надо всеми силами привлекать несложные математические выкладки и расчеты (см. также вычисления времени “засыпания” потока в примере 1 и смещения катящегося мяча в примере 3). И не только потому, что классическая педагогика всегда подчеркивала важность межпредметных связей, но еще и потому, что в ходе проводимых реформ образования все чаще провозглашается невозможность постижения основ этого важного предмета учениками, выбирающими нематематические специальности. Аргументация порой доходит до курьезов. Недавно в Интернете прочел в обсуждении одной из статей, посвященных содержанию обучения, следующий пассаж. Женщина (бухгалтер по профессии!) пишет, что в школе люто ненавидела математику и до сих пор не понимает, что такое синус. Но процентную-то меру налогов она как бухгалтер не может не понимать! А синус, который есть отношение величин катета и гипотенузы, по сути мало чем отличается от начисления процентов подоходного налога, ну разве что синус не принято выражать в процентах! Конечно, это частности, но почему-то в бурно развивающейся Юго-Восточной Азии преподаванию математики и естественных наук уделяется все возрастающее внимание, а не наоборот... Всесторонне подготовившись, можно приступать к написанию программы аплета (см. листинг 4).
Особенностью данного аплета является
то, что потоки создаются не как реализация у
аплета интерфейса Основой работы аплета является класс Примечание. Рекомендуем сравнить сказанное выше с расположением описаний классов в примере 3 в части II, где мы также строили иерархию классов, — там все классы размещаются автономно. Класс Далее записан текст конструктора,
задающего начальные координаты луча и
запускающего поток, который данный луч будет
рисовать. Непосредственно заниматься рисованием
будет следующая далее процедура под названием Примечание. Важно подчеркнуть, что
хотя все величины, входящие в формулу y0 * (1
– x/F), являются целыми, наличие деления делает
вычисления в классе целых чисел некорректными.
Бороться с этим можно единственным, хотя и
несколько специфическим способом: надо с самого
начала дать понять Java-машине, что вычисления
ведутся с вещественными числами. Для этого
переменную Метод Мы разобрали внутреннее устройство
класса Как следует далее из листинга 4,
создается пять экземпляров лучей Подчеркнем, что каждый из лучей Описанный пример весьма красив и вполне достоин завершить наше предварительное знакомство с языком Java. И если хотя бы некоторая часть читателей заинтересовалась им и напишет какие-либо свои аплеты, тем более с учениками, автор будет считать, что время, потраченное на подготовку и написание данной публикации, было потрачено не напрасно. Рис. 8. Многопотоковый аплет Ссылки1. Харольд Э.Р. 10 причин, по которым нам нужен Java 3. http://www.javaportal.ru/articles/10_reasons_Java3.html. 2. Bruce Eckel. Thinking in Java, 2nd Ed. Русский перевод доступен по адресу http://www.uic.rsu.ru/doc/programming/java/TIJ2e.ru/ 3. Нотон П., Шилдт Г. Полный справочник по Java. Киев: Диалектика, 1997, 592 с. 4. Java Thread Primitive Deprecation. http://java.sun.com/j2se/1.4.2/docs/guide/misc/threadPrimitiveDeprecation.html. 5. Еремин Е.А. Программное обеспечение для изучения объектного подхода в курсе информатики. // Сборник трудов конференции “Информационные технологии в образовании”. М., 1999. Ч. 2, с. 41–43. 6. Jones P.E. Section 4.11 — Neko example. http://www.csse.uwa.edu.au/~peterj/javalin/tut4-l9.html. 7. Lemay L., Perkins C.L., Morrison M. Teach Yourself Java in 21 Days. http://docs.rinet.ru/J21/ch11.htm (figure 11.3 — рисунки с фазами движений). 8. It's Neko! (3.0). http://webneko.net/ 1 В науке возможность наложения явлений принято называть суперпозицией. 2 В момент протяжки для уменьшения негативных визуальных эффектов объектив перекрывался специальной заслонкой. 3 Неудивительно, что если в такой список добавится пара-тройка вирусов, это пройдет незамеченным.
4 Над этим мало кто задумывается, но
серия из 100 операторов a[1] := 0; a[2] := 0; … 5 Более современный вариант — встреча громоздких иномарок в узком переулке. 6 Неко по-японски и есть котенок.
Е.. А.. Еремин,
| ||||