    Глава 5 

    Графика 

    Возможность использовать Java в апплетах, передаваемых через "всемирную 
паутину" (World Wide Web — WWW), привела к резкому росту популярности языка 
Java. Большинство апплетов WWW включают в себя интерактивную анимацию и 
звуковое сопровождение, т.е. как раз то, что без Java платформ-но-независимым 
способом выполнить трудно. Таким образом, возможность создания 
визуально-привлекательных анимационных апплетов в настоящее время является 
одной из важнейших особенностей Java. 
    В этой главе описано, как можно выполнять визуализацию в Java — отображать 
тексты, графические объекты и изображения. Познакомившись с примерами 
апплетов, приведенных в данной главе, и прочитав подробные объяснения к ним, 
вы научитесь делать свои апплеты более привлекательными для пользователей WWW. 
А если вы научитесь применять те же графические средства в Java-программах, то 
Java станет вашим любимым языком создания нестандартных пользовательских 
интерфейсов. С помощью Java они создаются и проще, и быстрее, чем с помощью 
других языков, которые использовались раньше в операционных системах, 
работающих с окнами. 
    Конечно, графика и пользовательский интерфейс — это только внешняя 
оболочка большинства программ. Основная их работа проходит незаметно для 
пользователя. Лишь в немногих программах достаточно большое внимание уделяется 
забавным изображениям и анимации. Но без них программа теряет 
конкурентоспособность. Она не привлечет внимания пользователя, и он обратится 
к другой, конкурирующей программе. По этой причине в багаже программиста 
обязательно должны быть сведения о графической системе Java. К счастью, 
графическая система проста, удобна в применении и легкоусвояема. В Java 
устранено большинство сложностей, обычно возникающих при программировании 
графики. Тем не менее с помощью этого языка можно создавать вполне 
профессиональные графические программы. 

    V. 
    145 

    Графика и поверхности отображения 

    В Java графика и текст при воспроизведении выводятся на некоторую 
поверхность отображения (display surface). Обычно это двухмерная плоскость 
пикселей. Например, это может быть сам экран, страница принтера или битовая 
карта в памяти. В любом случае поверхность отображения представляет собой 
плоскость, на которой формируется изображение. 
    Однако все названные поверхности отображения различны с точки зрения 
операционной системы. Битовая карта — это массив байтов в памяти. При 
установке цвета определенного пикселя вы фактически устанавливаете значение 
определенного байта или нескольких байтов в памяти. 
    Изображение, которое пользователь видит на экране, создается еще более 
сложным образом. По своей сути это — массив байтов в видеопамяти компьютера. 
Экраном обычно управляет операционная система. Выбор цвета для определенного 
пикселя на самом деле представляет собой сложную последовательность вызовов 
операционной системы, которые модифицируют значения в видеопамяти. В 
частности, система проверяет, не закрыта ли та позиция, которую модифицируют, 
дочерним или другим окном и можно ли вообще воспроизвести требуемый цвет в 
данной системе. 
    Для создания изображения страницы, предназначенной для вывода на принтер, 
хотя оно и подобно изображению на экране или битовой карте в "'памяти, 
требуются специальные драйверы. К тому же принтеры различаются как аппаратно, 
так и по своему программному обеспечению. Поэтому при выводе изображения на 
принтер для установки цвета в определенной позиции страницы требуются весьма 
сложные средства. (Чтобы описать их, понадобилась бы целая глава.) 
    Для вывода текстов и графики на любую поверхность отображения вам, конечно 
же, хотелось бы иметь один универсальный набор методов (называемый API — 
Application Programming Interface). Тогда независимо от поверхности 
отображения использовался бы один-единственный интерфейс. Представьте, что вам 
надо писать различные коды при выводе изображения на лазерные, матричные и 
струйные принтеры. Абсурдно, не правда ли? 
    В классе Graphics как раз и содержится один API, необходимый для 
манипулирования поверхностями отображения всех типов. (Возможно, это не 
идеальный API, но он хорош в качестве основы и его можно настраивать.) Каждый 
объект класса Graphics ассоциирован, или присоединен, к одной из поверхностей 
отображения. Для вывода того или иного компонента на экран используется объект 
класса Graphics, связанный с ограничивающим прямоугольником компонента. Для 
отображения геометрической фигуры в память используется объект класса 
Graphics, присоединенный к битовой карте. Аналогично при отображении страницы, 
предназначенной для печати, используется API объекта Graphics, связанного с 
поверхностью отображения для принтера. Один и тот же интерфейс используется 
для рисования на этих различных, но концептуально очень похожих поверхностях отображения. 

    146	Visual J++: основы программирования 

    На рис. 5.1 показано, как для манипулирования поверхностями отображения 
различных видов используется один API класса Graphics. 

    Поверхность отображения — битовая карта 


    Рис. 5.1. API класса Graphics используется для различных поверхностей отображения 


    Получение объекта класса Graphics 

    Графическое изображение при выводе на экран формируется в пределах 
какого-либо объекта, поскольку весь экран — это иерархия компонентов и 
контейнеров. Даже сам экран можно представить в виде большого контейнера, 
содержащего все другие компоненты. Рисование на экране происходит в 
ограничивающем прямоугольнике отдельного компонента. 
    Как было отчасти показано в главе 3, при отображении графики на экране 
используются методы update()/paint() класса Component. Экраном управляет 
система Java совместно с операционной системой. Они отслеживают данные о том, 
какой компонент какой ограничивающий прямоугольник занимает, какой компонент 
перекрывает другие компоненты, когда в последний раз перерисовывались 
различные области экрана. Когда система Java обнаруживает, что данную область 
экрана надо обновить (или "перерисовать"), она создает объект класса Graphics, 
присоединенный к этому прямоугольнику экрана, и передает объект 
соответствующему методу updateQ компонента или компонентов. Синтаксис метода 
update() следующий: 

    public v**^ update(Graphics g) 

    У этого метода имеется только один параметр — объект класса Graphics. Этот 
объект присоединен к некоторой области ограничивающего прямоугольника 
отображаемого компонента. 
    Есть несколько причин, по которым система может принять решение о 
необходимости перерисовать изображение в ограничивающем прямоугольнике 

    Глава 5. Графика 147 

компонента. Предположим, например, что программа отображает модальное 
диалоговое окно, ожидая, пока пользователь не нажмет кнопку"ОК. При нажатии 
указанной кнопки диалоговое окно исчезает. В этот момент система стремится как 
можно быстрее перерисовать все компоненты, которые были скрыты модальным 
диалоговым окном. Область, которую оно закрывало, объявляется недостоверной 
(invalid), следовательно, ее необходимо перерисовать. 
    Чтобы перерисовать прямоугольник компонента, система вызывает метод 
updateQ компонента. Когда метод завершается, система автоматически начинает 
считать прямоугольник достоверным. 
    Вместо того чтобы ждать, пока система определит, что область экрана 
недостоверна и ее надо перерисовать, можно задать системе отмечать компоненты 
как недостоверные, используя метод repaint(). Реализация этого метода 
"заставляет" систему Java считать компонент недостоверным и как можно раньше 
вызывать метод updateQ. 
    По умолчанию метод updateQ вызывает метод paint() компонента. Обычно все 
рисование проводится методом paint(), а не методом updateQ. Иногда Java 
вызывает метод paintQ явно, а не пользуется для этого методом updateQ. Чтобы 
обработать компонент в данной ситуации так же хорошо, как и в ситуации с 
вызовом метода updateQ, надо реализовать метод paintQ, поскольку он 
запускается в обоих случаях. 
    Таким образом, способ получения объекта класса Graphics, связанного с 
определенной частью экрана, базируется на реализации метода paint() объекта 
(или объектов), чей ограничивающий прямоугольник расположен на этой части 
экрана, ^^cjj^suiju^ffijebj^oj}^^ repaintQ. 

    Объекты класса Graphics 

    Объект класса Graphics можно считать набором инструментов, предназначенным 
для рисования на определенной поверхности отображения. С помощью этих 
инструментов можно отображать линии, прямоугольники, овалы, дуги, 
многоугольники, тексты и даже содержимое некоторых других поверхностей 
отображения (разрешается копировать изображение, содержащееся в памяти, на 
другую поверхность отображения). Используя эти элементарные графические 
операции, можно нарисовать гораздо более сложные фигуры. 
    У объекта класса Graphics имеется ряд внутренних переменных состояния, 
определякнцих, как будет нарисовано изображение. Например, у объекта класса 
Graphics есть внутренний текущий цвет (current color), используемый при 
рисов^щи простых фигур. Когда рисуется линия, ее цвет определяется текущим 
цветом объекта. При рисовании заполненного прямоугольника снова используется 
текущий цвет объекта. Не используется текущий цвет только при переносе готовых 
изображений на данную поверхность. В этом случае собственные цвета изображений 
точно воспроизводятся на данной поверхности отображения, независимо от 
текущего цвета объекта класса Graphics. 

    148	Visual J++: основы программирования 

    В следующей таблице представлены гге^е^енньге^остояния класса Graphics и 
их краткое описание. Далее будет более подробно рассказано о том, как эти 
переменные влияют на использование объекта класса Graphics. 
    
    
    Переменная состояния     Описание______________________ 
    Surface	Поверхность отображения, связанная с объектом класса Graphics. 
Всем объектам этого класса соответствует своя поверхность отображения. Однако 
у поверхности отображения может быть более одного связанного с ней объекта. 
    Current color	Цвет, используемый при рисовании на поверхности 
    отображения. 
    Clipping rectangle	Область отсечения, в которой может отображаться 
    "объект класса Graphics. Вне данной области поверхность отображения не изменяется. 
    Current font	Шрифт, используемый для вывода текста на поверхность 
отображения. Текст отображается текущим цветом. 
    Painting mode	Режим paint или XOR. Графические функции в режиме простого 
рТШЯзЖия изменяют текущие значения пикселей поверхности отображения. Функции в 
режиме XOR осуществляют операцию исключающего ИЛИ с текущим значением цвета 
пикселей, используя альтернативный цвет. 
    Origin	Начало.координат в графической координатной системе. Это точка 
(0,0) — обычно левый верхний угол поверхности отображения, хотя ее можно и 
переместить. 

    Текущий цвет 

    Текущий цвет — это цвет, используемый объектом класса Graphics для 
рисования чего-либо (кроме уже готовых изображений) на поверхности 
отображения. Все линии, прямоугольники, многоугольники, тексты и прочее 
отображаются с использованием текущего цвета объекта. 
    Цвета в Java определяются с помощью объектов типа Color. Такой объект 
просто описывает красную, зеленую и синюю составляющие цвета, т.е. точку 
цветового пространства RGB. 
    Для созданщг объекта класса Color следует передать значения красной, синей 
и зеленой составляющих конструктору класса Color следующим образом: 

    Color  col^rRed =  new Color (255,   0,   0);   //  Полностью красный. 

    Каждая составляющая цвета описывается одним байтом, значит, можно 
создать около 16 миллионов цветов. Однако система ограничена количеством 
цветов, поддерживаемых аппаратными средствами. Если какой-то цвет недоступен, 
система будет искать ближайший к нему. Можно создавать цвет с использованием 
32-битового целого для отображения цвета в формате RGB. 

    Глава 5. Графика 149 

    Такое целое значение описывается шаблоном OxOOrrggbb. Младший байт — это 
информация о синем компоненте, следующий — о зеленом, третий — значение 
красного компонента. Старший байт носит название альфа (alpha) или байта 
прозрачности, хотя в настоящее время в Java он не используется. Вот пример 
использования 32-битового целого для создания цветовой переменной: 

    Color colorYellow = new Color(OxOOFFOOOO) /* Красный. */ | 
    OxOOOOFFOO /* Зеленый. */); 

    Для получения отдельных значений красной, зеленой и синей составляющих 
данного объекта класса Color используются методы getRedQ, getGreenQ и getBlueQ 
класса Color. _Метод RGB() класса Color возвращает 32-битовое целое значение 
цвета по описанной выше схеме. 
    В классе Color имеется ряд цветовых констант, объявленных как public. Они 
определяют цвета, которые наиболее часто используются: красный, зеленый, 
синий, серый, белый, черный, фиолетовый, голубой и т.д. Для использования этих 
цветов надо просто сослаться на них следующим образом: 

    Color myRedRef = Color.red; Color myWhiteRef = Color.white; 

    Далее речь пойдет об использовании цветов в объекте класса Graphics. Метод 
getColorQ класса Graphics возвращает значение текущего цвета, а метод 
setColorQ устанавливает новое значение текущего цвета. Ниже приведен пример 
реализации метода paintQ для гипотетического компонента. Этот метод при 
изображении компонента использует более светлый цвет, чем тот, которым 
компонент был нарисован: 

    public void paint(Graphics g) { // Получение текущего цвета. Color 
colorCurrent = g.getColor(); 
    // Создание нового, более светлого цвета. 
    Color colorLighter = new Color(colorCurrent.getRed() + 5, 
    colorCurrent.getGreen() + 5, 
    colorCurrent.getBlue() + 5)); 


    Область отсечения 

    Графические объекты ограничены в своем действии областью отсечения. То 
ест^функции отображения, воздействующие на пиксели вне области отсечения, не 
дают результатов. 
    Область отсечения особенно полезна при рисовании компонента на экране. 
Методу paintQ компонента передается объект класса Graphics, присоединенный к 
экрану. Без области отсечения компонент мог бы рисовать в произвольном месте, 
вне своего ограничивающего прямоугольника. Это 

    150	Visual J++: основы программирования 

означает, что метод paintQ компонента мог бы рисовать поверх смежных 
компонентов, заголовков меню главного окна типа Frame и даже поверх любой 
части экрана вообще. Это могло бы привести к неприятностям. 
    Разумеется, размер области отсечения устанавливается, самое большее, таким 
же, как размер ограничивающего прямоугольника компонента. Если компонент 
пытается рисовать где-то вне области отсечения, то рисование проводится 
вхолостую. Компонент может рисовать лишь в пределах области отсечения (рис. 5.2). 

    Область 
    отсечения, 
    полностью 
    находящаяся 
    в ограничивающем 
    прямоугольнике 
    Ограничивающий прямоугольник 

    Рис. 5.2. Область отсечения компонента должна быть не больше, чем его 
ограничивающий, прямоугольник 

    Даже при однократном обновлении компонента методы update()/paint() для 
него могут вызываться несколько раз. Это происходит, например, в случае, когда 
компонент перекрывается другим компонентом. Система разбивает плоскость 
компонента на несколько прямоугольников, как показано на рис. 5.3. Для каждого 
из этих меньших прямоугольников системой один раз вызывается метод 
update()/paint(), устанавливающий область отсечения объекта на оди.н из 
меньших прямоугольников. Таким образом, компонент может рисовать в своей 
области отсечения, но не может рисовать поверх какой-лцбо части другого 
компонента или поверх любой другой части экрана! "^!~ 

    Глава 5. Графика 151 


    I Область отсечения 1 
    • Область отсечения 2 
    Компонент 2 
    Компонент 1: видимая область состоит из двух областей отсечения 

    Рис. 5.3. В видимой области компонента могут находиться несколько областей отсечения 

    Метод getClipRectQ класса. Graphics возвращает объект типа Rectangle, чьи 
переменные описывают ширину, высоту и координаты текущей области отсечения. 
Координаты определяют точку начала прямоугольника. 
    Метод setClipRectQ класса Graphics используется для изменения размеров 
области отсечения объекта класса Graphics. Совершенно очевидно, что нельзя 
сделать область отсечения больше, чем размеры самого объекта. Это нарушило бы 
всю концепцию применения области отсечения. В этой главе уже говорилось о том, 
что компонент не может рисовать вне ограничивающего прямоугольника. Если бы 
было возможным создать область отсечения объекта класса Graphics, 
передаваемого методу paintQ, больше допустимой, то такой объект смог бы 
рисовать вне своего ограничивающего прямоугольника. Область отсечения объекта 
после вызова метода setClipRectQ становится равной пересечению области 
отсечения до вызова и прямоугольника^ переданного методу setClipRectQ. На рис. 
5.4 показано, как формируется результирующий прямоугольник. 

    1   Первоначальная область отсечения 
    2  Метод setClipRectang!e() объекта пытается :     создать область отсечения 
    :3	Область отсечения, полученная в результате 
    ;	пересечения двух прямоугольников, всегда 
    :	меньше первоначальной области отсечения 

    Рис. 5.4. Область отсечения всегда меньше ограничивающего прямоугольника 


    152 Visual J++: основы программирования 

    Использование области отсечения ускоряет процесс отображения компонента: 
рисуется только то, что находится внутри области отсечения. Допустим, 
например, что компонент при каждом перерисовывании отображает сетку. Если 
компонент пытается рисовать только линии сетки, которые появляются в области 
отсечения, то удается избежать многих ненужных вызовов графических методов. 
Далее приведен код, демонстрирующий этот принцип. Сначала показано, как можно 
рисовать сетку, игнорируя область отсечения: 

    public  void paint(Graphics  g) { Dimension   size   =   sizeO; 
    //   Рисование   вертикальных  линий  через  каждые   10   пикселей, //   
начиная  с   5   столбца. 
    for(int  col  =  5;   coloize .width;   col+=10) g.drawLine(   /*   от, 
точки  */   col,   0, 
    /*  до точки  */  col,   size.height); 
    // Рисование горизонтальных линий через каждые 10 пикселей, // начиная с 5 столбца. 
    for(int row = 5; row<size.height; row+=10) g.drawLine( /* от точки */ 0, row, 
    /* до точки */ size.width, row); } 

    Работа сделана. Но если надо за короткое время многократно обновлять 
изображение, то можно освободиться от ненужных вызовов метода drawLine() в 
циклах. Вместо этого рисуются только линии, проходящие через область 
"отсечения:

    public void paint(Graphics g) {   -	.         , 
    Dimension size = size ();•**•	-'I  (AC 1^1^^Щ''1^^ 
    Rectangle rectClip^= g.cjetClipRect () ;	IfajQ <A?/?f $ fr{# */%? 
    // Рисование горизонтальных линий через каждые 10 пикселей // внутри 
области отсечения.	"~ for (int col=( (rectClip.x+4)/10)*10 + 5; 
    col < rectClTp.x+rectClip.width; col+=10) g drawLine ( /* от точки */ col, 
0,	n  N"""'' /* до точки */ col, size.height ); ^ 
    // Рисование горизонтальных линий через каждые 10 пикселей 
    // внутри области отсечения. 
    for (int row=((rectClip.y+4) /10) *10+5; 
    row<rectClip.y+rectClip.width; row+=10) g^urawLine( /* от точки */ 0, row, 
    /* до точки */ size.width, row); 


    Глава 5. Графика	153 


    Текущий шрифт 

    Понятие шрифта отображаемого текста включает в себя понятия гарнитуры, 
размера и стиля. При отображении текста с помощью объекта класса Graphics 
используют текущий шрифт объекта. 
    Что такое гарнитура? Это точное описание внешнего вида каждой буквы 
алфавита. Например, гарнитура Courier имеет фиксированную ширину букв и 
используется в этой книге для представления кодов: 

    The Courier typeface looks like this. 

    Размер шрифта указывает точную величину отдельных букв. Чем больше его 
размер, тем больше отображаемые буквы. Концепция размера шрифта заимствована в 
основном от наборщиков, имеющих огромный опыт в этой области. К сожалению, 
заимствован также и старомодный способ описания размеров шрифтов, 
заключающийся в применении пунктов. Пункт приблизительно равен 1/72 дюйма. 
Из-за использования этой странной единицы измерения задавать величины 
отображаемых символов в Java несколько неудобно, хотя и не очень сложно. 
Конечно, читателям, имеющим опыт работы в системе Windows, где надо отображать 
текст в окне, приходилось сталкиваться с вычислением в пунктах и они прекрасно 
знают, каким наказанием это может быть. Но каким облегчением будет узнать, что 
в Java работа со шрифтом не так сложна, как с использованием Windows SDK и 
MFC-классов Visual C++! 

    Размер шрифта 

    Как указано выше, размер шрифта измеряется в пунктах. На рис. 5.5 показаны 
основные метрики шрифтов. В качестве одного из параметров конструктора при 
создании шрифта следует указывать его размер (то есть высоту). 

    Ширина символа Баз< 
    ^яли„ияГ r^i«ir^_Ihe.iquickiif6wrLiQxIirZ_~________~_ 
    Heightp - -JJ?!^ent-^5- ______!_J._________________________________
    ________ |_ .Leadi ng_ <=гъ_ _________________________________________ 
    Jumped over the lasy programmer. 
    < 

    Puc. 5.5. Метрики шрифта В следующей таблице представлены метрики шрифтов: 


    MeTWifta      Описание 

    Ascent	Расстояние (в пунктах) между базовой линией и верхней точкой 
выступающего вверх символа. Выступающие символы, такие как А, t и h, выше, чем 
обычные — а, с. 

    154	Visual J++: основы программирования 

    Метрика      Описание 

    Descent	Расстояние (в пунктах) между базовой линией и нижней точкой 
выступающего вниз символа. Такие символы, как q, j, g, заканчиваются ниже 
базовой линии. 
    Leading	Расстояние между смежными строками. Измеряется в пунктах. 
    Height  Суммарное значение трех предыдущих расстояний. 

    Перевод из пунктов в пиксели — утомительная и скучная работа, 
осо-беннсГесли надо написать код, способный работать в нескольких приложениях 
и апплетах. Для этой цели в Java имеется класс FontMetrics, родственный классу 
Font. Объект класса FontMetrics описывает параметры отображаемого текста в 
пикселях; при этом ему передаются шрифт и объект класса Graphics, который 
будет использоваться для отображения текста. 
    Шрифт можно представить в двух разных объектах класса Graphics различными 
размерами в пикселях. Скажем, один объект связан с прямоугольником на экране, 
а другой — со страницей матричного принтера. Но так как все шрифты матричного 
принтера приводятся к одному размеру, один шрифт одного и того же текста может 
иметь два разных размера в пикселях в двух разных объектах класса Graphics. 
Поэтому объект типа FontMetrics описывает размеры конкретного шрифта в 
конкретном объекте типа Graphics. 
    Метод getFontMetricsQ класса Graphics возвращает метрики текущего шрифта, 
используемого для поверхности отображения данного объекта. Другими словами, 
возвращаемый объект FontMetrics описывает размер (в пикселях) текста, 
представленного в объекте класса Graphics, с использованием текущего шрифта объекта. 
    Методы класса FontMetrics используются для точного измерения размеров букв 
в пикселях. Метод getAscentQ возвращает величину выступающей вверх части 
шрифта. Метод getDescentQ возвращает величину выступающей вниз части шрифта. 
Метод GertLeadingQ возвращает рекомендуемое расстояние между нижней точкой 
символов, опускающихся ниже базовой линии, и верхней точкой выступающих 
символов, расположенных на следующей строке. Это дополнительное пространство 
между базовыми линиями смежных строк текста. Метод getHeightQ возвращает сумму 
значений, возвращаемых методами getAscendQ, getDescendQ и getLeadingQ, то есть 
расстояние, которое надо оставлять между базовыми линиями текста. Метод 
charWidthQ возвращает ширину (в пикселях) одного символа текста. Метод 
stringWidth() возвращает ширину всей строки представленного текста. Есть и 
другие похожие мФефды, поэтому передавать объект типа String необязательно — 
вместо него можно передать массив символов (charWidthQ) или массив байтов 
(byteWidth()). Наконец, метод getWidths() возвра щает массив, состоящий из 256 
целых чисел, который описывает ширину всех символов шрифта. 
    Далее представлен апплет, который использует класс FontMetrics для 
определения того, где на экране надо отобразить текст. У него имеется 

    Глава 5. Графика	155 

    произвольное число параметров, называемых "lineX". Апплет отображает эти 
строки одну за другой, оставляя между ними расстояние, рекомендуемое классом 
FontMetrics. Красным отображаются те строки, которые выходят за границы 
апплета, цветом объекта — те строки, которые не выходят за его границы. На 
рис. 5.6 представлен внешний вид окна апплета с несколькими типичными строками 
текста в качестве значений параметров lineX. Для создания этого апплета в 
Visual J++ надо создать новый проект с одним файлом с расширением JAVA и, 
прежде чем компилировать проект, скопировать в этот файл нижеследующий код. 
Надо создать собственный HTML-файл и добавить строки с параметрами в тег 
<APPLET> или использовать вкладку Debug диалогового окна Settings, чтобы 
установить параметры в случае вызова объекта отладчиком Visual J++. 

    import Java.applet...*; 'import java.awt.*; 
    public class FontMetricsApplet extends Applet { String!] m_astrLines;	""" 
    public void initO ( int cLines; for (cLines=0; null != 
getParameter("line"+cLines); cLines++); 
    m_astrLines = new String[cLines]; for(int ii=0; ii<cLines; ii++) 
    m__astrLines[ii] = getParameter("line"+ii); 
    }	, ^" 
    public void paint(Graphics g) ( 
    FontMetrics fm = g.getFontMetrics() ; Dimension size = sizef); 
    for (int ii=0; ii<m astrLines . lencjtji; ii++) { int ex = 
fm.stringWidth(m_astrLines[ii]); g.setColor(getForeground() ) ; if(ex >= size.width) 
    g.setColor(Color.red); g.drawstring(m_strLines[ii], 5, 
    "fm.getHeight()* (ii + 1))) ; 


    156	Visual J++: основы программирования 


    Рис. 5.6. Окно апплета Font Metrics 

    Для создания представленного на рис. 5.6 окна использован следующий HTML-файл: 

    <HTML> 
    <HEAD> 
    <TITLE>FontMetricsApplet Example</TITLE> 
    </HEAD> 
    <BODY> 
    <Hl>FontMetricsApplet Example</Hl> 
    <HR> 
    <APPLETCODE=" FontMetricsApplet.class" WIDTH="400" Height="300"> 
    <PARAM NA^E="lineO" VALUE="This is the first'"llnew> "  """ 
    <PARAM NAME="linel" VALUE="The quick brown fox"> 
    <PARAM NAME="line2" VALUE="jumped over the lazy long line of text 
    that runs on and on and on and on"> <PARAM NAME="line3" VALUE=""> 
    <PARAM NAME=«*line4" VALUE="This example demonstrates how"> <PARAM 
NAME="line5" VALUE="to use the FontMetrics"> </APPLET? 
    "*•%.. 
    </BODY> </HTML> 


    Глава 5. Графика 157 


    Стили шрифтов 

    У шрифтов есть внешние атрибуты стиля, которые можно добавить к гарнитуре. 
Эти атрибуты — italic (курсив) и bold (полужирный). В настоящее время в Java 
используются только эти атрибуты стиля. Они задаются с помощью констант 
Font.ITALIC и Font.BOLD класса Font, которые посредством операции побитового 
ИЛИ могут быть указаны при задании стиля. 
    Наряду с названием гарнитуры и размером шрифта атрибуты стиля передаются 
конструктору класса Font для создания новых шрифтов. 

    Гарнитуры 

    Как уже говорилось, гарнитура шрифта — это описание внешнего вида букв 
алфавита. Приведенный выше пример гарнитуры Courier служит еще и примером 
гарнитуры, обычно доступной на всех платформах. Это значит, что можно быть 
твердо уверенным в наличии гарнитуры Courier на любой платформе, где 
запускается апплет или приложение Java. 
    В классе Toolkit, входящем в пакет AWT, имеется метод, который возвращает 
исчерпывающий список гарнитур, находящихся в распоряжении системы. Этот метод 
— метод getFontListQ — возвращает массив строк. Каждый элемент этого массива 
является названием одной из гарнитур, имеющихся в системе. Ниже приведена 
программа, перечисляющая все гарнитуры текущей системы: 

    import java.awt.*; 
    public class ListFonts { 
    public static void main(String astrArgs[]) { 
    String astrFonts[] = Toolkit.getDefaultToolkit() .getFontList(); 
    for(int ii=0; ii<astrFonts.length; ii + + ) 
    System.out.println("Font" + ii + ": " + astrFonts[ii]); 


    Создание ШРИФТОВ 

    Теперь вам должно быть понятно, как следует использовать имеющиеся и 
создавать новые шрифты в Java. Конструктор класса Font использует три 
аргумента: название гарнитуры, размер шрифта (размер в пунктах, а точнее 
размер выступающей части в пунктах) и его атрибуты. Вот как можно создать 
шрифт гарнитуры Courier с высотой в 10 пунктов и атрибутом Italic: 
    Font   f  =*^hew  Font("Courier",   Font.ITALICS,    10); 
    Методу setFont() класса Graphics передается объект типа Font. Этот 
объект^становится текущим шрифтом для объекта класса Graphics, и все 
последующие операции с текстом используют этот шрифт для отображения текста на 
поверхности отображения. 
    Далее приведен пример, в котором вся информация о шрифте помещена в одну 
большую программу. Апплет FontGallery использует модифицированную 

    158	Visual J++: основы программирования 

версию класса TabbedPanel, приведенного в главе 3, в качестве исходного 
интерфейса. Кода этого класса здесь нет, он доступен по WWW и может быть 
получен с узла Visual J++ Handbook. Существенное различие между этой 
модифицированной версией и версией из главы 3 заключается в том, что в 
рассматриваемой версии можно передавать любой из уже имеющихся компонентов в 
качестве вкладки вместо того, чтобы заново создавать изображения. Различные 
вкладки окна TabbedPanel представлены в виде кнопок, у каждой из которых есть 
название — гарнитура шрифта, имеющаяся в системе. При щелчке мышью на кнопке в 
нижней части апплета на табло появляется пример строк текста, набранных данным 
шрифтом. На рис. 5.7 показано окно этого апплета. 

    Рис. 5.7. Окно апплета FontGallery 

    import  j atra . awt. * ; import  Java.applet.*; 
    * class  FontDisplayPanel  extends   Panel   { 
    Font  m_font; 
    Font m_fdtitltal; 
    Font m_fontBold; 
    Font^m_fontBoldItal; 
    privatlS,String m_strSampleText; 
    public FontDisplayPanel (Font f, String s) { m_font = f; 
    m_fontltal = new Font(f.getName(), ~~      f.getStyleO I Font.ITALIC, f.getSizeO) 
    w 


    Глава 5. Графика 159 

    m_fontBold = new Font(f.getName(), 
    f.getStyleO I Font. BOLD, f.getSize()); m_fontBoldltal = new Font(f.getName(), 
    ~ f.getStyle()| Font.BOLD | Font.ITALIC,f.getSize()); m_strSampleText = s; } 
    public void paint(Graphics g) { // Выбор шрифта, g.setFont(m_font); 
    // Получение метрик шрифта. 
    int су = g.getFontMetrics().getHeight(); 
    // Отображение образца текста обычным стилем, курсивом, // полужирным и полужирным курсивом, g.drawstring(m_strSampleText, 5, су); 
    setFont(m_fontltal); 
    д.drawstring(m_strSampleText, 5, cy*2); 
    setFont(m_fontBold); 
    g.drawstring(m_strSampleText, 5, cy*3); 
    setFont(m_fontBoldltal); 
    g.drawstring(m_strSampleText, 5, cy*4); 
    public class FontGallery extends Applet { public void init() { TabbedPanel tp; 
    // Создание интерфейса TabbedPanel. 
    setLayout(new BorderLayout()); 
    add("Center", tp = new TabbedPanel()); 
    // Получение списка шрифтов. Создание обычного 
    // (не-bold, не-italics) экземпляра для каждого шрифта. 
    // Создание для него закладки. 
    String[] astrFonts = Toolkit.getDefaultToolkit().getFontList(); 
    for (int ii=0; ii<astrFonts.length; ii + + ) ( 
    Font f = new Font(astrFonts[ii], Font.PLAIN, 10); ^    
tp.addTab(astrFonts[ii], new Button(astrFonts[ii]), 
    newFontDisplayPanel(f, "The slow gray fox sneezed.")); 


    Графические режимы 

    Существуют два графических режима. Первый, интуитивно знакомый всем, это 
режим ^рисования (paint mode). Второй, требующий пояснений, называется XOR-режимом. 

    16О Visual J++: основы программирования 

    В режиме рисования все графические операции копируют текущий цвет объекта 
класса Graphics в некоторые пиксели поверхности отображения. Например, при 
рисовании линии (предположим, что часть линии находится внутри области 
отсечения) значение текущего цвета копируется в память, отведенную пикселям, 
которые лежат на рисуемой линии. Если линия проходит через верхнюю часть 
области отсечения, то все пиксели этой линии будут иметь текущий цвет.
    В этом режиме объект класса Graphics устанавливает цвет пикселей в 
текущий. Вы можете спросить: "Что особенного в этом режиме?" Ничего. Просто 
это именно то, чего вы ожидаете при рисовании"на"экранё^———" 
    В XOR-режиме цвет формируется несколько иначе. В нем для создания нового 
цвета комбинируются три различных цвета. Это текущий цвет, ^фугой цвет, 
хранящийся в объекте класса Graphics и называемый альтернативным цветом XOR, и 
цвет пикселя, который был установлен до начала графической операции. Над этими 
тремя величинами производится операция побитового сравнения XOR, в результате 
чего образуется новое 32-битовое значение цвета для пикселя в системе RGB. 
    XOR-режим можно использовать для проведения множества специальных операций 
отображения, которые трудно осуществить в режиме простого рисования. Например, 
при работе в этом режиме имеется возможность вывести поверх изображения рамку 
выбора какого-нибудь его фрагмента. Конечно, можно нарисовать такую рамку, 
просто подобрав цвет, например белый или черный. Во многих случаях такой поход 
сработает, но что если в изображении пр_еобладает этот же цвет? Пользователь 
фактически не увидит эту рамку. 
    В этом случае приходит на помощь XOR-режим. Часть рамки_на белом 
изображении получится черной, на зеленом — желтой, на оранжевом — синей. На 
рис. 5.8 показана такая рамка. 

    Рис. 5.8. Использование XOR-режима для создания рамки выбора 


    Глава 5. Графика 161 

    Для создания изображения используется следующий код: 

    import java.awt.*; import Java.applet.*; import java.net.*; 
    public class SelectionRect extends Applet { Rectangle m_rectSelection; 
Image m_img; 
    public void init() { 
    // Предполагаем, что параметр "image" присутствует. 
    try { 
    m_img = getlmage(new URL(getDocumentBase().toStringO + 
    getParameter("image"))); m_rectSelection = null; 
    } catch(HalformedURLException e) { 
    /* обработка исключения */} 
    // Переопределение метода update(), поскольку в его // унаследованной 
реализации фон стирается, public void update () { paint(g); 
    public void paint(Graphics g) { 
    // Рисование изображения. Необходимо убедиться, что // текущий режим — 
нормальный. g.drawlmage(m_img, О, О, this); g.setPaintMode(); 
    // Если это рамка выбора, ее надо 
    // рисовать в XOR-режиме. 
    if(null != m_rectSelection) { 
    Color colorAlt = new Color(-getForeground().getRGB()) 
    g.setXORMode(colorAlt); 
    g.drawRect(m_rectSelection.x, m_rectSelection.y, 
    m rectSelection.width, m_rectSelection.height); 
    7Г 
    *N»* 
    Обработка перемещения мыши для отслеживания положения рамки. 
    :*•"* ***** 
    v  public boolean mouseDown(Event evt, int x, int y) { m_rectSelection = 
new Rectangle(x, y, 0, 0); repaint(); return true; 


    162 Visual J++: основы программирования 


    public boolean mouseUp(Event evt, int x, int y) { m_rectSelection = null; 
repaint(); return true; 
    public boolean mouseDrag(Event evt, int x, int y) ( 
    m_rectSelection .width + = x - m_rect_Selection . x; 
    m_ rectSelection.height += у - m_rectSelection.у; 
    repaint(); 
    return true; } 


    Отображение графики 

    В классе Graphics имеются методы, объявленные как public, с помощью 
которых можно рисовать линии, прямоугольники, заполненные прямоугольники, 
многоугольники, заполненные многоугольники, овалы, заполненные овалы, дуги, 
сектора, различные изображения. Как и где отображаются эти элементы — 
определяется внутренним состоянием объекта класса Graphics на момент вывода на 
экран. Например, заполненные прямоугольники рисуются в виде прямоугольного 
блока текущего цвета объекта класса Graphics. Положение и размеры 
прямоугольника задаются относительно начала координат объекта. На изображение 
прямоугольника может влиять и режим рисования. В следующих параграфах подробно 
рассказано, как рисуется каждый из названных элементов. 

    Отображение пиний 

    Отображение линии является простейшей операцией для любой графической 
среды программирования. В Java, чтобы нарисовать линию, используется метод 
drawLineQ класса Graphics. Четыре параметра этого метода определяют две точки, 
т.е. координаты х и у для каждого из двух концов отрезка. Это действительно 
очень просто: 

    public void paint (Graphics g) { 
    // Проводим линию между левым верхним и правым нижним краями 
    // объекта. 
    g.drawLine(0,   0,   size() .width-1,   size() .height-1); 
    >	% 


    Отображение ПРЯМОУГОЛЬНИКОВ 

    Хотя прямоугольник является частным случаем многоугольника, в классе 
Graphics имеются методы отображения и многоугольников, и прямоугольников. 

    Глава 5. Графика 163 

    Связано это, по-видимому, с тем, что основные операции заполнения и 
рисования 1трямо^о?№икхш„,пршодятся намного быстрее, чем соответствующие 
операции для многоугольников. 
    Для изображения контура прямоугольника используется метод drawRectQ класса 
Graphics. Ему передаются параметры х, у, высота и ширина прямоугольника. Метод 
fillRectQ изображает заполненный прямоугольник или прямоугольный блок в 
текущем цвете объекта класса Graphics. Например, в следующем коде показано, 
как можно нарисовать прямоугольник, расположенный в центре ограничивающего 
прямоугольника компонента: 


    public class MyCanvas extends Canvas ( 
    public void paint(Graphics g) ( 
    Rectangle rectBounds = bounds(); 
    // Уменьшение размеров прямоугольника на 10 пикселей, // если размеры 
прямоугольника позволяют сделать это. rectBounds 
^jrowJMath.max(-size().width/2, -10), Math.max (-size() .height/2, -10)); 
    // Изображение прямоугольника. 
    g.fillRect(rectBounds.x, rectBounds.y, rectBounds.width, rect.Bounds.height); 


    Отображение МНОГОУГОЛЬНИКОВ 

    Многоугольник, который надо нарисовать, можно определить двумя способами. 
В первом методам drawPolygon() или fiHPolygon() предоставляются два массива Х- 
и Y- координат точек многоугольника: 

    int aXCoords| int aYCoords[ 
    = {10, 50, 50, 15) = (10, 15, 50, 50} 
    д.drawPolygon(aXCoords, aYCoords, 4); 

    Следует заметить, что указывать первую точку повторно нет необходимости 
—^ъект_автоматически завершит многоугольник, соединив_первую и по^лед]щою_точки. 
    В пакете AWT имеется класс Polygon, который можно использовать для задания 
формы многоугольника. Перегруженные версии методов drawPoly-gon^yt 
fillPolygon() класса Graphics вместо массива координат, показанного в первбти 
примере, принимают в качестве параметров объекты класса Polygon. Конструктору 
класса Polygon передаются координаты вершин многоугольника. Метод addPointQ 
класса Polygon можно использовать, чтобы динами^ чески добавлять вершины 
многоугольника. В следующем примере апплет отслеживает щелчки мыши и 
использует их для динамического построения 

    164 Visual J++: основы программирования 

    многоугольника. Каждый новый щелчок мыши — это новая вершина 
многоугольника, которую должен изобразить апплет. 

    import  java.awt.*; 
    import Java.applet.Applet; 
    public class PolygonDrawer extends Applet { int x[] = [0, 10, 15); int y[] 
= [0, 10, 15); Polygon m_poly = new Polygon(x, y, 3) ; 
    public void paint(Graphics g) { 
    // Изображение заполненного многоугольника. 
    g.fillPolygon(m_poly) ; } 
    // В месте каждого щелчка мышью добавляется новая // точка многоугольника, 
public boolean mouseDown(Event evt, int x, int y) { 
    m_poly.addPoint(x, y); 
    repaint (); 
    return true; } 

    Из рис. 5.9 видно, что метод, используемый в Java для заливки 
многоугольников, заполняет только замкнутые области. При изображении, скажем, 
пентаграммы будут заполнены ёеТсрЗя;* а центр останется пустым, поскольку 
стороны фигуры пересекаются. Использованный алгоритм называется "нечетно-четным". 

    Рис. 5.9. Многоугольник, нарисованный объектом класса Graphics 


    Глава 5. Графика 165 

    Объекты класса Polygon целесообразно использовать по двум причинам. 
Во-первых, они дают возможность хранить набор координат X и Y в одном объекте. 
Поэтому нет необходимости писать код для управления массивом координат X и 
массивом координат Y. Во-вторых, в классе Polygon имеется несколько методов, 
которые могут оказаться полезными. Метод getBound-ingBoxQ класса Polygon 
возвращает наименьший из прямоугольников, охватывающих многоугольник. Метод 
insideQ класса Polygon возвращает значение true, если заданная точка находится 
внутри многоугольника. Уже упоминавшийся "нечетно-четный" алгоритм, 
применяемый при заливке многоугольника методом fillPolygonQ класса Graphics, 
используется и методом inside() для определения того, где находится точка — 
внутри многоугольника или вне его. 
    Можно создавать новые методы для объектов класса Polygon. В частности, вы 
можете написать метод move() и добавить его в класс, порожденный от класса 
Polygon. Этот метод будет сдвигать все вершины многоугольника на некоторое 
расстояние вдоль осей X и Y. Аналогично можно реализовать методы rotateQ и 
scaleQ. Общие переменные класса Polygon — npoints, xpointsf] и ypointsf] — 
определяют вершины многоугольника, и ими можно непосредственно манипулировать 
при помощи методов порожденного класса, как показано в следующем примере 
реализации метода move(): 

    class SuperPolygon extends Polygon { 
    public SuperPolygon(int xpoints[], int ypoints[],int npoints) ( 
    super(xpoints, ypoints, npoints); } 
    public void move(int dx, int dy) { 
    for(int ii=0; ii<npoints; ii-n-) { xpoints[ii] += dx; ypoints[ii] += dy; 


    Отображение овалов 

    С помощью методов drawOvalQ и fillOvalQ класса Graphics можно изобразить 
овал или круг. Оси овала ориентированы вдоль осей X и Y. Это означает, что 
нельзя нарисовать овал, расположенный по диагонали относительно поверхности 
отображения. Вообще-то это сделать можно, но придется рисовать его в виде 
связанных линий или в виде многоугольника с множеством вершин,— т.е. 
изображать овал "вручную". 
    Tfplj изображении овала определяют ограничивающий его прямоугольник. Если 
этот прямоугольник оказался квадратом, то овал будет кругом. На рис. 5.10 
показан овал и ограничивающий его прямоугольник, изображенные с помощью кода, 
приведенного ниже: 

    166 Visual J++: основы программирования 


    public void paint(Graphics g) ( 
    Rectangle r = new Rectangle(10, 10, 50, 100) g.setXORMode(Color.red) ; 
    g.fillRect(r.x, r.y, r.width, r.height); g.drawOval(r.x, r.y, r.width, r.height); 


    Рис. 5.10. Овал и ограничивающий его прямоугольник 


    Отображение эллиптических дуг 

    Дуга считается частью овала. Методы drawArcQ и fillArcQ класса Graphics 
используются в основном так же, как и методы drawOvalQ и fillOvalQ. Чтобы 
нарисовать дугу, необходимо указать размеры ограничивающего прямоугольника, 
начальный угол и разницу между начальным и конечным углами. (Углы задаются в 
радианах.) Если последний параметр равен 2л, то получится целый овал, если же 
он равен п — половина овала. 
    Начальный угол дуги измеряется относительно положения, соответствующего 
трем часам на часовом циферблате. Положительные углы откладываются по Часовой 
стрелке, отрицательные — против часовой стрелки. Аналогично, положительная 
величина дуги откладывается по часовой стрелке от начального угла, а 
отрицательная — против. 
    Объявления методов drawArc() и fillArc() одинаковы и выглядят следующим 
образом: * "*й, 

    public void drawArc(int x, int у, int width, int height, 
    // Определение прямоугольника. 
    double startAngle)""double arcDistanceT?' public void fillArctint x, int 
y, int width, int height, 
    // Определение прямоугольника, double startAngle, double arcDistance); 


    Глава 5. Графика 167 


    Рисование изображений 

    С помощью метода drawImageQ класса Graphics изображение может быть 
скопировано на поверхность отображения, связанную с объектом класса Graphics 
Процесс копирования содержащегося в оперативной памяти изображения на 
поверхность отображения часто называют bitblting ('bit billing") или biting. 
(Это слово является аббревиатурой от термина "bit block transferring" — 
поблочная передача битов.) Вывод изображения осуществляет компонент типа 
ImageCanvas. Его переопределенные методы minimumSize() и prefferdSizeQ 
возвращают размер изображения, переданный конструктору: 

    import  java.awt.*; 
    public class ImageCanvas extends Canvas { private Image m_img; 
    public ImageCanvas (Image img) { m img = img; 
    public Dimension preferredSize() { 
    return new Dimension(m_img.getWidth(this) , m_img.getHeight(this)); } 
    public Dimension minimumSize() { 
    return preferredSize(); } 
    public void paint(Graphics g) { 
    g.drawlmage(m_img, 0, 0, this); 
    } 

    У метода drawImageQ класса Graphics имеются четыре параметра. Первые три 
параметра очевидны: изображение, которое надо нарисовать, а также X-и 
Y-координаты того места на поверхности отображения, связанной с объектом 
класса Graphics, где это изображение должно быть нарисовано. 
    Четвертым параметром является объект класса Image Observer. Подробное 
описание данного параметра можно найти в электронной версии документации 
Visual J++. Вкратце же можно сказать следующее. Изображения могут быть 
загружены через Internet. Поскольку такие изображения, как правило, находятся 
в файлах с расширением GIF или JPG, которые могут быть довольнсиобъемными. 
Java загружает их асинхронно. Особый объект типа ImageObserver 
контролирует~вып6лнение операции загрузки. Например, в предыдущем примере 
класс ImageCanvas использует методы getWidth() и getHeight() miacca*!4mage для 
определения ширины и высоты рисуемого изображения. Поскольку в момент вызова 
этих методов высота и ширина изображения могут быть неизвестными (ведь система 
еще не закончила загрузку изображения по Internet), то им следует передать 
объект ImageObserver. Когда указанные параметры станут известными, объект 
ImageObserver получит уведомление об этом. Разумеется, если к моменту вызова 
методов длина и 

    168 Visual J++: основы программирования 

ширина известны, то параметр ImageObsejyerjjjaaojJSByeTCH. Класс Component 
реализует интерфеис"1та§еОЬ8егуёг7^оэтому класс ImageCanvas (типа 
Сотро-Йёй^аследуёт реализацию интерфейса IrnageObserver. 

    Анпмаипя 

    Анимация — демонстрация непрерывного ряда последовательных изображений — 
позволяет достичь эффекта "движущейся картинки", создающей у пользователя 
иллюзию непрерывного движения. Примером анимации может служить мультфильм с 
взаимодействующими персонажами, видеоигра (например, Pinball или игра, 
имитирующая сражение в космосе), видеоклип, демонстрирующий взаимодействие 
молекул в процессе образования нового вещества. 
    В Visual ]++ имеются примеры хорошей анимации, выполненные фирмой Sun. На 
рис. 5.11, 5.12 и 5.13 показаны соответственно окна апплетов Animator, 
GraphLayout и MoleculeViewer. В каждом из них демонстрируется последовательный 
ряд изображений, полученных с использованием приемов анимации. 

    Рис. 5.11. Апплет Animator 


    Глава 5. Графика 169 


    Рис. 5.12. Апплет GraphLayout 

    Рис. 5.13. Апплет Molecule Viewer 


    170 Visual J++: основы программирования 

    Технология анимации в Java довольно проста. Имеется всего лишь два_ 
способа, которые можно применять для достижения хорошего качества анимации. 
Чтобы разобраться в данном вопросе, необходимо изучить апплет_ Animator фидмы 
Sun. Первый способ   заключается в упорядочении серии предварительно 
загруженных изображений с использованием фоновых программных потоков. Второй, 
более сложный способ заключается во взаимодействии   нескольких  фоновых   
программных   потоков  для   создания   и отображения кадров анимации. Апплет 
Animator представляет собой пример использования первого метода, поэтому в 
следующем параграфе сначала разберем его код. 

    Упорядочение кадров анпмашш: апплет Animator 

    Апплет Animator предназначен для показа простой анимации путем вывода на 
экран ряда предварительно загруженных изображений. Каждый 
^адр_анимашш-,сз!др,еяедяе1ся .своим номером (от 0 до N), изображением, 
ддительнйстью показа изображения в миллисекундах, местоположением, в котором 
надо показать изображение, и аудиофайлом, проигрываемым во время демонстрации 
каждого кадра. Помимо этого можно указать несколько глобальных параметров 
апплета Animator, включая параметр типа HREF (благодаря которому апплет при 
щелчке на нем мышью будет вести себя подобно гиперсс,ылке), цвет фона и 
фоновую музыку (фоновые и кадровые аудиофайлы автоматически смешиваются для 
создания звукового сопровождения апплета). Главное, на что следует обратить 
внимание, это как апплет использует фоновый поток для упорядочения кадров анимации. 
    Сначала необходимо скопировать апплет Animator из Info Viewer и открыть 
его. Для копирования и открытия проекта следует выполнить следующее: 
    1.   Запустить Visual J++. Закрыть все проекты при помощи опции Close 
Workspace меню File, в результате чего появится основное оглавление InfoViewer. 
    2.   В разделе Samples основного оглавления открыть подраздел Sun Samples 
с расположенным в нем документом Animator. Чтобы загрузить все файлы проекта, 
надо щелкнуть мышью на кнопке документа. Поступит запрос, куда следует 
загружать файлы, и будет предложена директория MSDEVtSAMPLES\SUNSAMPLES\ANIMATOR. 
    3.   Открыть скопированный проект с помощью опции Open Workspace меню File 
главного меню. В диалоговом окне Open File указать, где находится каталог 
проекта. Для этого следует перейти в каталог 
MSDEV\SAM-PLES\S4JNSAMPLES\ANIMATOR, в который был скопирован проект, и 
открыть файл проекта Animator. 
    Далее следует просмотреть классы проекта Animator. Их три: Animator, 
Description Frame, ParseException. В данном случае два последних класса не 
рассматриваются. Представляет интерес только код, который используется классом 
Animator для показа последовательности изображений. 

    Глава 5. Графика 171 

    В апплете Animator для создания простого цикла анимации используется 
фоновый поток. Один и тот же метод можно применять в любом анимационном 
апплете для упорядочения анимационных кадров. Далее в виде псевдокода 
приведено описание действия фонового потока: 

    public class Animator extends Applet implements Runnable { Thread engine; 
int framenum = -1; 
    // В методе init() происходит загрузка всех изображений, 
    // аудиофайлов и всего необходимого для анимации, а также 
    // запуск потока фоновой анимации. 
    public void init() { 
    // Получение списка изображений, аудиофайлов и других 
    // необходимых ресурсов. 
    // Загрузка изображений и всего прочего, необходимого для 
    // анимации. 
    } 
    // В методе start() происходит запуск фонового потока 
    // engine. 
    public void start (} { 
    engine = new Thread(this); 
    engine.start (); 
    // В методе stop() происходит удаление ссылки на поток // engine. Поток 
распознает это действие и корректно завершается, public void stop(} { engine = null; 
    // Метод run() запускается фоновым потоком engine. // Именно в нем реально 
осуществляется вся анимация. public void run() ( 
    Хранение ссылки на текущий поток engine. 
    Если загрузка изображения еще не окончена, следует подождать. 
    Пока ссылка на текущий поток равна ссылке, хранящейся 
    в переменной engine { 
    »   приращение framenum на 1 
    repaint(); 
    текущий поток на некоторое время приостанавливается 
    //В методе paint() рисуется изображение кадра с номером // framenum. 
public void paint(Graphics g) ( 
    if(framenum >= 0)      // при инициализации равно -1 Рисование изображения 
с номером framenum на поверхности, связанной с объектом д. 


    172 Visual J++: основы программирования 

    В этом коде используются пять управляющих методов: 
    •    Метод initQ загружает изображения и другие данные, необходимые для 
создания анимации. 
    •	Метод startQ запускает фоновый программный поток. 
    •	Метод stop() останавливает фоновый программный поток 
    •	Метод paintQ рисует текущий кадр анимации. 
    •    Метод шп(), выполняемый фоновым потоком, непрерывно обновляет текущий 
кадр после некоторого ожидания. 
    Если вы будете просматривать список методов класса Animator с помощью 
утилиты ClassView Visual J++, то убедитесь, что целый ряд методов здесь не 
указан. В основном это вспомогательные методы для каждого из пяти 
перечисленных управляющих методов. В частности, в классе Animator есть пять 
анализирующих методов. Они вызываются только методом initQ для анализа 
параметров апплета, поэтому методу init() известно, какие изображения и 
аудиофайлы следует загружать: метод parselmagesQ анализирует список 
изображений, создающих кадры анимации; метод parseDurations() определяет 
длительность каждого кадра; метод parseSoundsQ анализирует список аудиофайлов 
для каждого кадра и т. д. Метод initQ выполняет задачи, упомянутые в 
приведенном псевдокоде,— загружает все изображения и другие связанные с ними 
данные, необходимые для отображения каждого кадра анимации. Аналогично, 
большая часть других методов-помощников используется одним из перечисленных 
методов (initQ, startQ, stop(), paintQ и runQ). 

    Устранение мерианпя 

    Необходимо сделать одно существенное замечание для тех, кто собирается 
осуществлять анимацию или даже просто рисовать в Java. Языку Java 
присущ_недостаток, приводящий к неприятностям при быстром построении 
Тйображений. Метод updated перед вызовом метода paintQ автоматически стирает 
весь_компонент, используя цвет фона. Если не переопределить метод ypdateQ, то 
при воспроизведении анимации экран будет мерцать. 
    Чтобы избавиться 'от мерцания, метод updateQ следует переопределить 
следующим образом: 

    public voici, update (Graphics g) { paint(g); 
    Пвопмая буферизация 
    "%_ 

    Апплет Animator прекрасно подходит для создания анимации в тех случаях, 
когда имеется ШДЦо.СТ_ью, определенный, конечный набор кадров. Но что делать с 
динамической анимацией (например, в видеоигре типа Space Invaders, в которой 
пользователь может по желанию передвигать своего героя по экрану)? 
Фиксированное число изображений не позволяет выполнять 

    Глава 5. Графика 173 

интерактивную анимацию. Каждый из кадров анимации может храниться в виде 
статического изображения, но для создания динамической анимации небольшие 
изображения необходимо комбинировать "на лету". 
    Для создания динамической анимации при помощи метода двойной буферизации 
можно использовать основу апплета Animator. При двойной буферизации вместо 
загрузки серии изображений для последовательного отображения в памяти 
создаются отдельные кадры прямо во время процесса анимации. Для их вывода на 
экран можно задействовать тот же программный поток. Создаются эти кадры в 
памяти в то время, когда программа отображения находится в режиме ожидания. 
Когда программа анимации активизируется, она выводит не загруженное 
изображение, а готовый кадр. 
    При двойной буферизации используются не пять, а шесть управляющих методов. 
Дополнительный метод runFrameGeneration() запускается другим фоновым потоком. 
Этот метод отвечает за генерацию последовательных кадров анимации и их 
хранение в переменной класса. Два фоновых потока, анимационная программа и 
генератор кадров работают следующим образом. Генератор кадров заполняет буфер 
в памяти и ждет (сколь угодно долго), пока анимационная программа отобразит 
его. Поток анимационной программы ждет заполнения буфера, потом активизируется 
и рисует кадр, после чего опять переходит в режим ожидания до следующего 
заполнения буфера. 
    Использование одного буфера памяти облегчает программирование, но при этом 
плохо используются ресурсы процессора. Более эффективным будет позволить 
генератору кадров создавать столько кадров, сколько это возможно, и где-то 
хранить их. .Ашшацирнная программа активизируется с помощью таймера, получает 
из стека следующий кадр и отображает его, а затем снова переходит в режим 
ожидания. При этом генератор кадров никогда не ждет и не теряет время.	-...-_-.-.—-~.~. 
    Поскольку память не безгранична, последний метод на самом деле невыполним. 
Нельзя позволять генератору кадров выдавать их до тех пор, пока вся память не 
окажется заполненной. Вместо этого его можно подключить к запоминающему 
устройству, в котором может храниться N кадров. Генератор кадров, пытающийся 
записать туда (N+\)-n кадр, блокируется до тех пор, пока анимационная 
программа не заберет один кадр. Таким образом, в памяти всегда хранится не 
более ./V кадров анимации. 

    Использование объекта класса Graphics, связанного с изображением в памяти 

    Рисование в памяти подобно рисованию на экране: в обоих случаях 
используется объект класса Graphics, связанный с поверхностью отображения 
вцгамяти. Производится оно в порядке, описанном ниже. 
    1.   Сначала создайте изображение в памяти. Это позволит сделать любой 
компонент при помощи метода createlmage(), которому необходимо передать 
значения ширины и высоты. 

    Image img2blBuffer = createlmage(width, height); 


    174 Visual J++: основы программирования 

    2.  ^атем^получц11е..жзобр_аж_ение с помощью метода getGraphicsQ класса Image: 

    Graphics g2blBuffer = img2blBuffer.getGraphics (); 

    Созданное изображение имеет свой набор цветов и шрифтов. Начало 
изображения — это левый верхний угол образа, хранящегося в памяти. Размер 
области отсечения равен размеру всего образа, хранящегося в памяти. 
    Далее приведен псевдокод, демонстрирующий, как поток анимации и 
поток-генератор совместно реализуют механизм двойной буферизации (с 
использованием нескольких изображений в памяти). Здесь представлены только 
методы гап() двух этих потоков; методы initQ, stopQ и paintQ не отличаются от 
приведенных ранее. 

    public class MyAnimator extends Applet implements Runnable { Vector 
m_vectFrames = new Vector(); Thread m_threadAnimator; Thread 
m_threadFrameGenerator; Image m_imgNextFrame; 
    // Метод init() загружает необходимые ресурсы. 
    // Метод start () создает и запускает два фоновых потока. 
    // Метод stop() останавливает потоки путем устранения ссылок. 
    // Метод paint()забирает нулевой кадр из объекта // m_vectFrames и 
отображает его. 
    // Метод run () выясняет, какой поток запущен, //и вызывает 
соответствующий метод, public void run() ( 
    if(Thread.currentThread() == m_threadAnimator) 
    runAnimator(); if(Thread.currentThread() == m_threadFrameGenerator) 
    runGenerator (); } 
    // Метод runAnimator() выполняется только потоком Animator, public void 
runAnimator() { 
    пока текущий поток == m_threadAnimator 
    "вели в объекте m_vectFrames имеется хотя бы один кадр,  *~~7 
    то следует удалить нулевой кадр и сохранить его ^    в объекте 
m_imgNextFrames.	J "^S,  repaint () 
    Переход в режим ожидания. 
    public void runFrameGenerator() { 
    Если не проведена инициализация, подождать 


    Глава 5. Графика	175 

    до ее проведения. 
    while(m_threadFrameGenerator == current thread) Создание следующего кадра. 
Добавление его в N позицию объекта m vectFrames. 

    В качестве "домашнего задания" изучите работу апплета GraphLayout, 
входящего в примеры к Visual J++. В этом апплете для создания динамической 
анимации использован метод двойной буферизации. 

