|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Графические примитивы
При создании компонента, т. е. объекта класса Component, автоматически формируется его графический контекст (graphics context). В контексте размещается область рисования и вывода текста и изображений. Контекст содержит текущий и альтернативный цвет рисования и цвет фона — объекты класса color, текущий шрифт для вывода текста — объект класса Font. В контексте определена система координат, начало которой с координатами (0, 0) расположено в верхнем левом углу области рисования, ось Ох направлена вправо, ось Оу — вниз. Точки координат находятся между пикселами. Управляет контекстом класс Graphics или новый класс Graphics2D, введенный в Java 2. Поскольку графический контекст сильно зависит от конкретной графической платформы, эти классы сделаны абстрактными. Поэтому нельзя непосредственно создать экземпляры класса Graphics или Graphics2D. Однако каждая виртуальная машина Java реализует методы этих классов, создает их экземпляры для компонента и предоставляет объект класса Graphics методом getGraphics () класса Component или как аргумент методов paint() И update(). Посмотрим сначала, какие методы работы с графикой и текстом предоставляет нам класс Graphics. При создании контекста в нем задается текущий цвет для рисования, обычно черный, и цвет фона области рисования — белый или серый. Изменить текущий цвет можно методом setcoior (Color newCoior), аргумент newcoior которого — объект класса Color. Узнать текущий цвет можно методом getcolor (), возвращающим объект класса color. Цвет, как и все в Java, — объект определенного класса, а именно, класса color. Основу класса составляют семь конструкторов цвета. Самый простой конструктор: Color(int red, int green, int blue) создает цвет, получающийся как смесь красной red, зеленой green и синей blue составляющих. Эта цветовая модель называется RGB. Каждая составляющая меняется от 0 (отсутствие составляющей) до 255 (полная интенсивность этой составляющей). Например: Color pureRed = new Color(255, 0, 0); Color pureGreen = new Color(0, 255, 0); определяют чистый ярко-красный pureRed и чистый ярко-зеленый pureGreen цвета. Во втором конструкторе интенсивность составляющих можно изменять более гладко вещественными числами от 0.0 (отсутствие составляющей) до 1.0 (полная интенсивность составляющей): Color(float red, float green, float blue) Например: Color someColor = new Color(O.OSf, 0.4f, 0.95f); Третий конструктор Color(int rgb) задает все три составляющие в одном целом числе. В битах 16—23 записывается красная составляющая, в битах 8—15 — зеленая, а в битах 0—7 — синяя составляющая цвета. Например: Color с = new Color(OXFF8F48FF); Здесь красная составляющая задана с интенсивностью 0x8F, зеленая — 0x48, синяя — 0xFF. Следующие три конструктора Color(int red, int green, int blue, int alpha) Color(float red, float green, float blue, float alpha) Color(int rgb, boolean hasAlpha) вводят четвертую составляющую цвета, так называемую "альфу", определяющую прозрачность цвета. Эта составляющая проявляет себя при наложении одного цвета на другой. Если альфа равна 255 или 1,0, то цвет совершенно непрозрачен, предыдущий цвет не просвечивает сквозь него. Если альфа равна 0 или 0,0, то цвет абсолютно прозрачен, для каждого пиксела виден только предыдущий цвет. Последний из этих конструкторов учитывает составляющую альфа, находящуюся в битах 24—31, если параметр hasAipha равен true. Если же hasAipha равно false, то составляющая альфа считается равной 255, независимо от того, что записано в старших битах параметра rgb. Первые три конструктора создают непрозрачный цвет с альфой, равной 255 или 1,0. Седьмой конструктор Color(ColorSpace cspace, float[] components, float alpha) позволяет создавать цвет не только в цветовой модели (color model) RGB, но и в других моделях: CMYK, HSB, CIEXYZ, определенных объектом класса ColorSpace. Для создания цвета в модели HSB можно воспользоваться статическим методом getHSBColor(float hue, float saturation, float brightness). Если нет необходимости тщательно подбирать цвета, то можно просто воспользоваться одной из тринадцати статических констант типа color, имеющихся в классе color. Вопреки соглашению "Code Conventions" они записываются строчными буквами: black, blue, cyan, darkGray, gray, green, lightGray, magenta, orange, pink, red, white, yellow. Методы класса Color позволяют получить составляющие текущего цвета: getRedO, getGreenO, getBlue(), getAlphaO, getRGBO, getColorSpace (), getComponents (). Два метода создают более яркий brighter!) и более темный darker о цвета по сравнению с текущим цветом. Они полезны, если надо выделить активный компонент или, наоборот, показать неактивный компонент бледнее остальных компонентов. Два статических метода возвращают цвет, преобразованный из цветовой модели RGB в HSB и обратно: float[] RGBtoHSB(int red, int green, int blue, float[] hsb) int HSBtoRGB(int hue, int saturation, int brightness) Создав цвет, можно рисовать им в графическом контексте. Основной метод рисования drawLine(int xl, int yl, int х2, int y2) вычерчивает текущим цветом отрезок прямой между точками с координатами (xl, yl) и (х2, у2). Одного этого метода достаточно, чтобы, нарисовать любую картину по точкам, вычерчивая каждую точку с координатами (х, у) методом drawLine (x, у, х, у) и меняя цвета от точки к точке. Но никто, разумеется, не станет этого делать. Другие графические примитивы:
Класс Polygon рассмотрим подробнее. Этот класс предназначен для работы с многоугольником, в частности, с треугольниками и произвольными четырехугольниками. Объекты этого класса можно создать двумя конструкторами:
После создания объекта в него можно добавлять вершины методом addPoint(int x, int у) Логические методы contains () позволяют проверить, не лежит ли в многоугольнике заданная аргументами метода точка, отрезок прямой или целый прямоугольник со сторонами, параллельными сторонам экрана. Логические методы intersects () позволяют проверить, не пересекается ли с данным многоугольником отрезок прямой, заданный аргументами метода, или прямоугольник со сторонами, параллельными сторонам экрана. Методы getBounds() и getBounds2D() возвращают прямоугольник, целиком содержащий в себе данный многоугольник. Вернемся к методам класса Graphics. Несколько методов вычерчивают фигуры, залитые текущим цветом: fillRect(), f ill3DRect(), filiArco, fiiioval(), f iliPoiygon(), filiRoundRect(). У них такие же аргументы, как и у соответствующих методов, вычерчивающих незаполненные фигуры. Например, если вы хотите изменить цвет фона области рисования, то установите новый текущий цвет и начертите им заполненный прямоугольник величиной во всю область: public void paint(Graphics g)( Color initColor = g.getColor(); // Сохраняем исходный цвет g.setColor(new Color(0, 0, 255)); // Устанавливаем цвет фона // Заливаем область рисования g.fillRect(0, 0, getSizeO.width - 1, getSize().height - 1);' g.setColor(initColor); // Восстанавливаем исходный цвет // Дальнейшие действия } Как видите, в классе Graphics собраны только самые необходимые средства рисования. Нет даже метода, задающего цвет фона (хотя можно задать цвет фона компонента методом setBackground() класса component). Средства рисования, вывода текста в область рисования и вывода изображений значительно дополнены и расширены в подклассе Graphics2D, входящем в систему Java 2D. Например, в нем есть метод задания цвета фона setBackground(Color с). Перед тем как обратиться к классу Graphics2D, рассмотрим средства класса Graphics для вы вод а текста. Для вывода текста в область рисования текущим цветом и шрифтом, начиная с точки (х, у), в, классе Graphics есть несколько методов:
Четвертый метод выводит текст, занесенный в объект класса, реализующего интерфейс AttributedCharacteriterator. Это позволяет задавать свой шрифт для каждого выводимого симвбла: drawstring(AttributedCharacteriterator iter, int x, int y) Точка (х, у) — это левая нижняя точка первой буквы текста на базовой линии (baseline) вывода шрифта. Метод setFont(Font newFont) класса Graphics устанавливает текущий шрифт для вывода текста. Метод getFont () возвращает текущий шрифт. Как и все в языке Java, шрифт — это объект класса Font. Посмотрим, какие возможности предоставляет этот класс. Объекты класса Font хранят начертания (glyphs) символов, образующие шрифт. Их можно создать двумя Конструкторами:
Типографский пункт в России и некоторых европейских странах равен 0,376 мм, Точнее, 1/72 части французского дюйма. В англо-американской системе мер пункт равен 1/72 части английского дюйма, 0,351 мм. Этот-то пункт и применяется в компьютерной графике. Имя шрифта name может быть строкой с физическим именем шрифта, например, "Courier New", ИЛИ одна ИЗ строк "Dialog", "Dialoglnput",' "Monospaced", "Serif", "SansSerif", "Symbol". Это так называемые логические имена шрифтов (logical font names). Если name == null, то задается шрифт по умолчанию. Стиль шрифта style — это одна из констант класса Font:
Полужирный курсив (bolditalic) можно задать операцией побитового сложения, Font. BOLD | Font. ITALIC, как это сделано в листинге 8.3. При выводе текста логическим именам шрифтов и стилям сопоставляются физические имена шрифтов (font face name) или имена семейств шрифтов (font name). Это имена реачьных шрифтов, имеющихся в графической подсистеме операционной системы. Например, логическому имени "Serif" может быть сопоставлено имя семейства (family) шрифтов Times New Roman, а в сочетании со стилями — конкретные физические имена Times New Roman Bold, Times New Roman Italic. Эти шрифты должны находиться в составе шрифтов графической системы той машины, на которой выполняется приложение. Список имен доступных шрифтов можно просмотреть следующими операторами: Font[] fnt = Toolkit.getGraphicsEnvironment.getAHFonts(); for (int i = 0; i< fnt.length; i++) System.out.println(fnt[i].getFontName()); В состав SUN J2SDK входит семейство шрифтов Lucida. Установив SDK, вы можете быть уверены, что эти шрифты есть в вашей системе. Таблицы сопоставления логических и физических имен шрифтов находятся в файлах с именами
и т. д. Эти файлы должны быть расположены в JDK в каталоге jdkl.3\jre\lib или каком-либо Другом подкаталоге lib корневого каталога JDK той машины, на которой выполняется приложение. Нужный файл выбирается виртуальной машиной Java по окончании имени файла. Это окончание совпадает с международным кодом языка, установ- ленного в локали или в системном свойстве user.language (см. рис. 6.2). Если у вас установлена русская локаль с международным кодом языка "ru", то для сопоставления будет выбран файл font.properties.ru. Если такой файл не найден, то применяется файл font.properties, не соответствующий никакой конкретной локали. Поэтому можно оставить в системе только один файл font.properties, переписав в него содержимое нужного файла или создав файл заново. Для любой локали будет использоваться этот файл. В листинге 9.1 показано сокращенное содержимое файла font.propeities.ru из JDK 1.3 для платформы MS Windows. Листинг 9.1. Примерный файл font.properties.ru : # %W% %E% # Это просто комментарии # AWT Font default Properties for Russian Windows # # Три сопоставления логическому имени "Dialog": dialog.0=Arial,RUSSIAN_CHARSET dialog.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED dialog.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED # По три сопоставления стилям ITALIC, BOLD, ITALIC+BOLD: dialog.italic.0=Arial Italic,RUSSIAN_CHARSET dialog.italic.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED dialog.italic.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED dialog.bold.0=Arial Bold,RUSSIAN_CHARSET dialog.bold.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED dialog.bold.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED dialog.bolditalic.0=Arial Bold Italic,RUSSIAN_CHARSET dialog.bolditalic.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED dialog.bolditalic.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED # По три сопоставления имени "Dialoglnput" и стилям: dialoginput.0=Courier New,RUSSIAN_CHARSET dialoginput.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED dialoginput.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED dialoginput.italic.0=Courier New Italic,RUSSIAN_CHARSET # И так далее # # По три сопоставления имени "Serif" и стилям: serif.0=Times New Roman,RUSSIAN_CHARSET serif.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED serif.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED serif.italic.0=Times New Roman Italic,RUSSIAN_CHARSET # И так далее # Прочие логические имена sansserif. CMArial,RUSSIAN_CHARSET sansserif.l=WingDings,SVMBOL_CHARSET,NEED_CONVERTED sansserif.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED sansserif.italic. 0=Arial Italic,ROSSIAN_CHARSET # И так далее # monospaced.0=Courier New,RUSSIAN_CHARSET monospaced.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED monospaced.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED monospaced.italic.0=Courier New Italic,RUSSIAN_CHARSET # И так далее # Default font definition # default.char=2751 # for backword compatibility # Старые логические имена версии JDK 1.0 timesroman.0=Times New Roman,RUSSIAN_CHARSET helvetica.0=Arial,RUSSIAN_CHARSET courier.0=Courier New,RUSSIAN_CHARSET zapfdingbats.0=WingDings,SYMBOL_CHARSET # font filenames for reduced initialization time # Файлы со шрифтами filename.Arial=ARIAL.TTF filename.Arial_Bold=ARIALBD.TTF filename.Arial_Italic=ARIALI.TTF filename.Arial_Bold_Italic=ARIALBI.TTF filename.Courier_New=COUR.TTF filename.Courier_New_Bold=COURBD.TTF filename.Courier_New_Italic=COURI.TTF filename.Courier_New_Bold_Italic=COURBI.TTF filename.Times_New_Roman=TIMES.TTF filename.Times_New_Roman_Bold=TlMESBD.TTF filename.Times_New_Roman_Italic=TIMES3.TTF filename.Times_New_Roman_Bold Italic=TIMESBI.TTF filename.WingDings=WINGDING.TTF filename.Symbol=SYMBOl.TTF # name aliases # Псевдонимы логических имен закомментированы # alias.timesroman=serif # alias.helvetica=sansserif # alias.courier=monospaced # Static FontCharset info # # Классы преобразования символов в байты fontcharset.dialog.0=sun.io.CharToByteCP1251 fontcharset.dialog.l=sun.awt.windows.CharToByteWingDings fontcharset.dialog.2=sun.awt.CharToByteSymbol fontcharset.dialoginput.0=sun.io.CharToByteCP1251 fontcharset.dialoginput.l=sun.awt.windows.CharToByteWingDings fontcharset.dialoginput.2=sun.awt.CharToByteSymbol fontcharset.serif.0=sun.io.CharToByteCP1251 fontcharset.serif.l=sun.awt.windows.CharToByteWingDings fontcharset.serif.2=sun.awt.CharToByteSymbol fontcharset.sansserif.0=sun.io.CharToByteCP1251 fontcharset.sansserif.l=sun.awt.windows.CharToByteWingDings fontcharset.sansserif.2=sun.awt.CharToByteSymbol fontcharset.monospaced.0=sun.io.CharToByteCP1251 fontcharset.monospaced.l=sun.awt.windows.CharToByteWingDings fontcharset.monospaced.2=sun.awt.CharToByteSymbol # Exclusion Range info # # He просматривать в этом шрифте указанные диапазоны exclusion.dialog.0=0100-0400,0460-ffff exclusion.dialoginput.0=0100-0400, 0460-ffff exclusion.serif.0=0100-0400,04 60-ffff exclusion.sansserif.0=0100-0400, 0460-ffff exclusion.monospaced.0=0100-0400,0460-ffff # charset for text input # # Вводимые байтовые символы кодируются в кириллический диапазон # кодировки Unicode inputtextcharset=RUSSIAN_CHARSET Большая часть этого файла занята сопоставлениями логических и физических имен. Вы видите, что под номером 0:
Там, где указан стиль: dialog.italic, dialog.bold и т.д., подставлен соответствующий физический шрифт. В строках листинга 9.1, начинающихся со слова filename, указаны файлы с соответствующими физическими шрифтами, например: filename.Arial=ARIAL.TTF Эти строки необязательны, но они ускоряют поиск файлов со шрифтами. Теперь посмотрите на последние строки листинга 9.1. Строка exclusion.dialog.0=0100-0400, 0460-ffff означает, что в шрифте, сопоставленном логическому имени "Dialog" под номером 0, а именно, Arial, не станут отыскиваться начертания (glyphs) символов с кодами в диапазонах '\u0100' —'\u0400' и '\u0460' —'\uFFFF'. Они будут взяты из шрифта, сопоставленного этому имени под номером 1, а именно, WingDings. То же самое будет происходить, если нужные начертания не найдены в шрифте, сопоставленному логическому имени под номером 0. Не все файлы со шрифтами Unicode содержат начертания (glyphs) всех символов. Если нужные начертания не найдены и в сопоставлении 1 (в данном примере в шрифте WingDings), они будут отыскиваться в сопоставлении 2 (т. е. в шрифте Symbol) и т. д. Подобных сопоставлений можно написать сколько угодно. Таким образом, каждому логическому имени шрифта можно сопоставить разные диапазоны различных реальных шрифтов, а также застраховаться от отсутствия начертаний некоторых символов в шрифтах Unicode. Все сопоставления под номерами 0, 1, 2, 3, 4 следует повторить для всех стилей: bold, italic, bolditalic. Если в графической системе используются шрифты Unicode, как, например, в MS Windows NT/2000, то больше ни о чем беспокоиться не надо. Если же графическая система использует байтовые ASCII-шрифты как, например, MS Windows 95/98/ME, то следует позаботиться об их правильной перекодировке в Unicode и обратно. Для этого на платформе MS Windows используются константы Win32 API RUSSIAN_CHARSET, SYMBOL_CHARSET, ANSI_CHARSET, OEM_CHARSET И др., показывающие, какую кодовую таблицу использовать при перекодировке, так же, как это отмечалось в главе 5 при создании строки из массива байтов. Если логическим именам сопоставлены байтовые ASCII-шрифты (в примере это шрифты WingDings и Symbol), то необходимость перекодировки отмечается константой NEED_CONVERTED. Перекодировкой занимаются методы специальных классов charToByteCP1251, TiarToByteWingDings, CharToByteSyrnbol. Они указываются для каждого сопоставления имен в строках, начинающихся со слова fontcharset. Эти строки обязательны для всех шрифтов, помеченных константой NEED_CONVERTED. В последней строке файла указана кодовая страница для перекодировки в Unicode символов, вводимых в поля ввода: inputtextcharset = RUSSIAN_CHARSET Эта запись задает кодовую таблицу СР1251. Итак, собираясь выводить строку str в графический контекст методом drawstring о, мы создаем текущий шрифт конструктором класса Font, указывая в нем логическое имя шрифта, например, "Serif". Исполняющая система Java отыскивает в файле font.properties, соответствующем локальному языку, сопоставленный этому логическому имени физический шрифт операционной системы, например, Times New Roman. Если это Unicode-шрифт, то из него извлекаются начертания символов строки str по их кодировке Unicode и отображаются в графический контекст. Если это байтовый ASCII-шрифт, то строка str предварительно перекодируется в массив байтов методами класса, указанного в одной из строк fontcharset, например, CharToByteCP1251. Хорошие примеры файлов font.properties.ru собраны на странице Сергея Астахова, указанной во введении. Обсуждение этих вопросов и примеры файлов font.properties для X Window System даны в документации SUN J2SDK в файле docs/guide/intl /fontprop.html. Завершая обсуждение логических и физических имен шрифтов, следует сказать, что в JDK 1.0 использовались логические имена "Helvetica", "TimesRoman", "Courier", замененные В JDK 1.1 НЗ "SansSerif", "Serif", "Monospaced", соответственно, из лицензионных соображений. Старые имена остались в файлах font.properties для совместимости. При выводе строки в окно приложения очень часто возникает необходимость расположить ее определенным образом относительно других элементов изображения: центрировать, вывести над или под другим графическим объектом. Для этого надо знать метрику строки: ее высоту и ширину. Для измерения размеров отдельных символов и строки в целом разработан класс FontMetrics. В Java 2D класс FontMetrics заменен классом TextLayout. Его мы рассмотрим в конце этой главы, а сейчас выясним, какую пользу можно извлечь из методов класса FontMetrics. Класс FontMetrics является абстрактным, поэтому нельзя воспользоваться его конструктором. Для получения объекта класса FontMetrics, содержащего набор метрических характеристик шрифта f, надо обратиться к методу getFontMetrics (f) класса Graphics или класса Component. Подробно с характеристиками компьютерных шрифтов можно познакомиться по книге. Класс FontMetri.cs позволяет узнать ширину отдельного символа ch в пикселах методом charwidth(ch), общую ширину всех символов массива или под-массива символов или байтов методами getchars() и getBytes(), ширину целой строки str в пикселах методом stringwidth(str). Несколько методов возвращают в пикселах вертикальные размеры шрифта. Интерлиньяж (leading) — расстояние между нижней точкой свисающих элементов таких букв, как р, у и верхней точкой выступающих элементов таких букв, как б, и, в следующей строке — возвращает метод getLeading (). Среднее расстояние от базовой линии шрифта до верхней точки прописных букв и выступающих элементов той же строки (ascent) возвращает метод getAscent (}, а максимальное — метод getMaxAscent (). Среднее расстояние свисающих элементов от базовой линии той же строки (descent) возвращает метод getDescent (), а максимальное — метод getMaxDescent(). Наконец, высоту шрифта (height) — сумму ascent + descent + leading — возвращает метод getHeight (). Высота шрифта равна расстоянию между базовыми линиями соседних строк. Эти элементы показаны на рис. 9.1.
Рис. 9.1. Элементы шрифта Дополнительные характеристики шрифта можно определить методами класса LineMetrics из пакета java.awt.font. Объект этого класса можно получить несколькими методами getLineMetrics () класса FontMetrics. Листинг 9.2 показывает применение графических примитивов и шрифтов, а рис. 9.2 — результат выполнения программы из этого листинга. Листинг 9.2. Использование графических примитивов и шрифтов import java.awt.*; import j ava.awt.event.*; class GraphTest extends Frame{ GraphTest(String s) { super(s); setBounds(0, 0, 500, 300); setVisible(true); } public void paint(Graphics g){ Dimension d = getSizeO; int dx = d.width / 20, dy - d.height / 20; g.drawRect(dx, dy + 20, d.width - 2 * dx, d.height - 2 * dy - 20); g.drawRoundRect(2 * dx, 2 * dy + 20, d.width - 4 * dx, d.height -4 * dy - 20, dx, dy); g.fillArctd.width / 2 - dx, d.height - 2 * dy + 1, 2 * dx, dy - 1, 0, 360); g.drawArctd.width / 2 - 3 * dx, d.height - 3 * dy / 2 - 5, dx, dy / 2, 0, 360); g.drawArctd.width / 2 + 2 * dx, d.height - 3 * dy / 2 - 5, dx, dy / 2, 0, 360); Font fl = new Font("Serif", Font.BOLD(Font.ITALIC, 2 * dy); Font f2 = new Font ("Serif", Font.BOLD, 5 * dy / 2); FontMetrics fml = getFontMetrics(fl); FontMetrics fm2 = getFontMetrics(f2); String s1 = "Всякая последняя ошибка"; String s2 =« "является предпоследней."; String s3 = "Закон отладки"; int firstLine = d.height / 3; int nextLine = fml.getHeight(); int secondLine = firstLine + nextLine / 2; g.setFont(f2); g.drawstring(s3, (d.width-fm2.stringWidth(s3)) / 2, firstLine); g.drawLine(d.width / 4, secondLine - 2, 3 * d.width / 4, secondLine - 2); g.drawLine(d.width / 4, secondLine — 1, 3 * d.width / 4, secondLine - 1); g.drawLine(d.width / 4, secondLine, 3 * d.width / 4, secondLine); g.setFont(fl); g.drawstring(s1, (d.width - fml.stringWidth(si)) 12, firstLine + 2 * nextLine); g.drawString(s2, (d.width - fml.stringWidth(s2)) / 2, firstLine + 3 * nextLine); } public static void main(String[] args){ GraphTest f = new GraphTest(" Пример рисования"); f.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent ev){ System.exit(0); } }); } } В листинге 9.2 использован простой класс Dimension, главная задача которого — хранить ширину и высоту прямоугольного объекта в своих полях width и height. Метод getsize () класса component возвращает размеры компонента в виде объекта класса Dimension. В листинге 9.2 размеры компонента f типа GrapMest установлены в конструкторе методом setBounds() равными 500x300 пикселов. Еще одна особенность листинга 9.2 — для вычерчивания толстой линии, отделяющей заголовок от текста, пришлось провести три параллельные прямые на расстоянии один пиксел друг от друга. Как вы увидели из обзора класса Graphics и сопутствующих ему классов, средства рисования и вывода текста в этом классе весьма ограничены. Линии можно проводить только сплошные и только толщиной в один пиксел, текст выводится только горизонтально и слева направо, не учитываются особенности устройства вывода, например, разрешение экрана. Эти ограничения можно обойти разными хитростями: чертить несколько параллельных линий, прижатых друг к другу, как в листинге 9.2, или узкий заполненный прямоугольник, выводить текст по одной букве, получить разрешение экрана методом getScreenSize() класса Java.awt.Toolkit и использовать его в дальнейшем. Но все это затрудняет программирование, лишает его стройности и естественности, нарушает принцип KISS. В Java 2 класс Graphics, в рамках системы Java 2D, значительно расширен классом Graphics2D.
Рис. 9.2. Пример использования класса Graphics В систему пакетов и классов Java 2D, основа которой— класс Graphics2D пакета java.awt, внесено несколько принципиально новых положений.
С такими возможностями Java 2D стала полноценной системой рисования, вывода текста и изображений. Посмотрим, как реализованы эти возможности, и как ими можно воспользоваться. Правило преобразования координат пользователя в координаты графического устройства (transform) задается автоматически при создании графического контекста так же, как цвет и шрифт. В дальнейшем его можно изменить методом setTransform() так же, как меняется цвет или шрифт. Аргументом этого метода служит объект класса AffineTransform из пакета java.awt.geom, подобно объектам класса color или Font при задании цвета или шрифта. Рассмотрим подробнее класс AffineTransform. Аффинное преобразование координат задается двумя основными конструкторами класса AffineTransform: AffineTransform(double a, double b, double с, double d, double e, double f) AffineTransform (float a, float b, float c, float d, float e, float f) При этом точка с координатами (х, у) в пространстве пользователя перейдет в точку с координатами (а*х+с*у+е, b*x+d*y+f) в пространстве графического устройства. Такое преобразование не искривляет плоскость — прямые линии переходят в прямые, углы между линиями сохраняются. Примерами аффинных преобразований служат повороты вокруг любой точки на любой угол, параллельные сдвиги, отражения от осей, сжатия и растяжения по осям. Следующие два конструктора используют в качестве аргумента массив (а, ь, с, d, e, f} или (а, ь, с, d}, если e = f = о, составленный из таких же коэффициентов в том же порядке: AffineTransform(double[] arr) AffineTransform(float[] arr) Пятый конструктор создает новый объект по другому, уже имеющемуся, объекту: AffineTransform(AffineTransform at) Шестой конструктор — конструктор по умолчанию — создает тождественное преобразование: Af fineTransform () Эти конструкторы математически точны, но неудобны при задании конкретных преобразований. Попробуйте рассчитать коэффициенты поворота на 57° вокруг точки с координатами (20, 40) или сообразить, как будет преобразовано пространство пользователя после выполнения методов: AffineTransform at = new AffineTransform(-1.5, 4.45, -0.56, 34.7, 2.68, 0.01); g.setTransform(at); Во многих случаях удобнее создать преобразование статическими методами, возвращающими объект класса AffineTransform. getRotatelnstance (double angle) — возвращает поворот на угол angle, заданный в радианах, вокруг начала координат. Положительное направление поворота таково, что точки оси Ох поворачиваются в направлении к оси Оу. Если оси координат пользователя не менялись преобразованием отражения, то положительное значение angle задает поворот по часовой стрелке.
Метод createinverse () возвращает преобразование, обратное текущему преобразованию. После создания преобразования его можно изменить методами: setTransform(AffineTransform at) setTransform(double a, double b, double c, double d, double e, double f) setToIdentity() setToRotation(double angle) setToRotation(double angle, double x, double y) setToScale(double sx, double sy) setToShare(double shx, double shy) setToTranslate(double tx, double ty) сделав текущим преобразование, заданное одним из этих методов. Преобразования, заданные методами: concatenate(AffineTransform at) rotate(double angle) rotate(double angle, double x, double y) scale(double sx, double sy) shear(double shx, double shy) translate(double tx, double ty) выполняются перед текущим преобразованием, образуя композицию преобразований. Преобразование, заданное методом preconcatenate{AffineTransform at), напротив, осуществляется после текущего преобразования. Прочие методы класса AffineTransform производят преобразования различных фигур в пространстве пользователя. Пора привести пример. Добавим в начало метода paint о в листинге 9.2 четыре оператора, как записано в листинге 9.3. Листинг 9.3. Преобразование пространства пользователя // Начало листинга 9.2... public void paint(Graphics gr){ Graphics2D g = (Graphics2D)gr; AffineTransform at = AffineTransform.getRotatelnstance(-Math.PI/4.0, 250.0,150.0); at.concatenate( new AffineTransform(0.5, 0.0, 0.0, 0.5, 100.0, 60.0)); g.setTransform(at); Dimension d = getSize(); // Продолжение листинга 9.2 Метод paint () начинается с получения экземпляра д класса Graphics2D простым приведением аргумента gr к типу Graphics2D. Затем, методом getRotatelnstance о определяется поворот на 45° против часовой стрелки вокруг точки (250.0, 150.0). Это преобразование— экземпляр at класса AffineTransform. Метод concatenate о, выполняемый объектом at, добавляет к этому преобразованию сжатие в два раза по обеим осям координат и перенос начала координат в точку (100.0, 60.0). Наконец, композиция этих преобразований устанавливается как текущее преобразование объекта g методом setTransform(). Преобразование выполняется в следующем порядке. Сначала пространство пользователя сжимается в два раза вдоль обеих осей, затем начало координат пользователя — левый верхний угол — переносится в точку (100.0, 60.0) пространства графического устройства. Потом картинка поворачивается на угол 45° против часовой стрелки вокруг точки (250.0, 150.0). Результат этих преобразований показан на рис. 9.3.
Рис. 9.3. Преобразование координат Рисование фигур средствами Java2D Характеристики пера для рисования фигур описаны в интерфейсе stroke. В Java 2D есть пока только один класс, реализующий этот интерфейс — класс BasicStroke. Конструкторы класса BasicStroke определяют характеристики пера. Основной конструктор BasicStroke(float width, int cap, int join, float miter, float[] dash, float dashBegin) задает:
Остальные конструкторы задают некоторые характеристики по умолчанию:
Лучше один раз увидеть," чем сто раз прочитать. В листинге 9.4 определено пять перьев с разными характеристиками, рис, 9.4 показывает, как они рисуют. Листинг 9.4. Определение перьев import j ava.awt.*; import j ava.awt.geom. *; import j ava.awt.event.*; class StrokeTest extends Frame{ StrokeTest(String s) { super (s) ; setSize(500, 400); setvisible(true); addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent ev)( System.exit(0); } }); } public void paint(Graphics gr){ Graphics2D g = (Graphics2D)gr; g.setFont(new Font("Serif", Font.PLAIN, 15)); BasicStroke penl = new BasicStroke(20, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,30); BasicStroke pen2 = new BasicStroke(20, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); BasicStroke репЗ = new BasicStroke(20, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL); floatf] dashl = {5, 20}; BasicStroke pen4 = new BasicStroke(10, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL, 10, dashl, 0); float[] dash2 = (10, 5, 5, 5}; BasicStroke pen5 = new BasicStroke(10, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10, dash2, 0); g.setStroke(penl); g.draw(new Rectangle2D.Double(50, 50, 50, 50)); g.draw(new Line2D.Double(50, 180, 150, 180)); g.setStroke(pen2); g.draw(new Rectangle2D.Double(200, 50, 50, 50)); g.draw(new Line2D.Double(50, 230, 150, 230)); g.setStroke(реnЗ); g.draw(new Rectangle2D.Double(350, 50, 50, 50)); g.draw(new Line2D.Double(50, 280, 150, 280)); g.drawstring("JOIN_MITER", 40, 130); g.drawstring("JOIN_ROUND", 180, 130); g.drawstring("JOINJBEVEL", 330, 130); g.drawstring("CAP_BUTT", 170, 190); g.drawstring("CAP_ROUND", 170, 240); g.drawstring("CAP_SQUARE", 170, 290); g.setStroke(pen5); g.drawfnew Line2D.Double(50, 330, 250, 330)); g.setStroke(pen4); g.draw(new Line2D.Double(50, 360, 250, 360)); g.drawString("{10, 5, 5, 5,...}", 260, 335); g.drawstring("(5, 10,...}", 260, 365); } public static void main(String[] args){ new StrokeTest("Моя программа"); } }
Рис. 9.4. Перья с различными характеристиками После создания пера одним из конструкторов и установки пера методом setStroke о можно рисовать различные фигуры методами draw() и fill(). Общие свойства фигур, которые можно нарисовать методом draw о класса Graphics2D, описаны в интерфейсе shape. Этот интерфейс реализован для создания обычного набора фигур — прямоугольников, прямых, эллипсов, дуг, точек — классами Rectangle2D, RoundRectangle2D, Line2D, Ellipse2D, Arc2D, Point2D пакета java.awt.geom. В этом пакете есть еще классы Cubiccurve2D и QuadCurve2D для создания кривых третьего и второго порядка. Все эти классы абстрактные, но существуют их реализации — вложенные классы Double и Float для задания координат числами соответствующего типа. В Листинге 9.4 использованы классы Rectangle2D.Double И Line2d.Double для вычерчивания прямоугольников и отрезков. В пакете java.awt.geom есть еще один интересный класс — GeneralPath. Объекты этого класса могут содержать сложные конструкции, составленные из отрезков прямых или кривых линий и прочих фигур, соединенных или не соединенных между собой. Более того, поскольку этот класс реализует интерфейс shape, его экземпляры сами являются фигурами и могут быть элементами других объектов класса GeneralPath. Вначале создается пустой объект класса GeneralPath конструктором по умолчанию GeneralPath () или объект, содержащий одну фигуру, конструктором GeneralPath (Shape sh). Затем к этому объекту добавляются фигуры методом append(Shape sh, boolean connect) Если параметр connect равен true, то новая фигура соединяется с предыдущими фигурами с помощью текущего пера. В объекте есть текущая точка. Вначале ее координаты (0, 0), затем ее можно переместить в точку (х, у) методом moveTo (float x, float у). От текущей точки к точке (х, у) можно провести:
Текущей точкой после этого становится точка (х, у). Начальную и конечную точки можно соединить методом ciosePath(). Вот как можно создать треугольник с заданными вершинами: GeneralPath p = new GeneralPath(); p.moveTo(xl, yl); // Переносим текущую точку в первую вершину, p.lineTo(x2, y2); // проводим сторону треугольника до второй вершины, p.lineTo(x3, уЗ); // проводим вторую сторону, p.closePathf); // проводим третью сторону до первой вершины Способы заполнения фигур определены в интерфейсе Paint. В настоящее время Java 2D содержит три реализации этого интерфейса — классы color, GradientPaint и TexturePamt. Класс Color нам известен, посмотрим, какие способы заливки предлагают классы GradientPaint И TexturePaint. Классы GradientPaint и TexturePaint Класс GradientPaint предлагает сделать заливку следующим образом. В двух точках м и N устанавливаются разные цвета. В точке M(xi, yi) задается цвет cl, в точке Ы(х2, у2) — цвет с2. Цвет заливки гладко меняется от el к с2 вдоль прямой, соединяющей точки м и м, оставаясь постоянным вдоль каждой прямой, перпендикулярной прямой мы. Такую заливку создает конструктор GradientPaint(float xl, float yl, Color cl, float x2, float y2, Color c2) При этом вне отрезка мы цвет остается постоянным: за точкой м — цвет cl, за точкой ы — цвет с2. Второй конструктор GradientPaint(float xl, float yl, Color cl, float x2, float y2, Color c2, boolean cyclic) при задании параметра cyclic == true повторяет заливку полосы мы во всей заливаемой фигуре. Еще два конструктора задают точки как объекты класса Point2D. Класс TexturePaint поступает сложнее. Сначала создается буфер — объект класса Bufferedlmage из пакета java.awt. image. Это большой сложный класс. Мы с ним еще встретимся в главе 15, а пока нам понадобится только его графический контекст, управляемый экземпляром класса Graphics2D. Этот экземпляр можно получить методом createGraphics () класса Bufferedlmage. Графический контекст буфера заполняется фигурой, которая будет служить образцом заполнения. Затем по буферу создается объект класса TexturePaint. При этом еще задается прямоугольник, размеры которого будут размерами образца заполнения. Конструктор выглядит так: TexturePaint(Bufferedlmage buffer, Rectangle2D anchor) После создания заливки — объекта класса color, GradientPaint или TexturePaint — она устанавливается в графическом контексте методом setPaint (Paint p) и используется в дальнейшем методом fill (Shape sh). Все это демонстрирует листинг 9.5 и рис. 9.5. Листинг 9.5. Способы заливки import java.awt.*; import Java.awt.geom.*; import java.awt.image.*; import j ava.awt.event.*; class PaintTest extends Frame{ PaintTest(String s){ super(s) ; setSize(300, 300); setVisible(true); addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent ev){ System.exit(0); } }); } public void paint(Graphics gr){ Graphics2D g = (Graphics2D)gr; Bufferedlmage bi = new Bufferedlmage(20, 20, Bufferedlmage.TYPE_IMT_RGB); Graphics2D big = bi.createGraphics(); big.draw(new Line2D.Double(0.0, 0.0, 10.0, 10.0)); big.draw(new Line2D.Double(0.0, 10.0, 10.0, 0.0)); TexturePaint tp = new TexturePaint(bi, new Rectangle2D.Double(0.0, 0.0, 10.0, 10.0)); g.setPaint(tp); g.fil(new Rectangle2D. Double (50, 50, 200, 200)); GradientPaint gp = new GradientPaint(100, 100, Color.white, 150, 150, Color.black, true); g.setPaint(gp); g.filKnew Ellipse2D.Double (100, 100, 200, 200)); } public static void main(String[] args){ new PaintTest(" Способы заливки"); } }
Рис. 9.5. Способы заливки Вывод текста средствами Java 2D Шрифт — объект класса Font — кроме имени, стиля и размера имеет еще полтора десятка атрибутов: подчеркивание, перечеркивание, наклон, цвет шрифта и цвет фона, ширину и толщину символов, аффинное преобразование, расположение слева направо или справа налево. Атрибуты шрифта задаются как статические константы класса TextAttribute. Наиболее используемые атрибуты перечислены в табл. 9.1. Таблица 9.1. Атрибуты шрифта
К сожалению, не все шрифты позволяют задать все атрибуты. Посмотреть список допустимых атрибутов для данного шрифта можно методом getAvailableAttributes() класса Font. В классе Font есть конструктор FontfMap attributes), которым можно сразу задать нужные атрибуты создаваемому шрифту. Это требует предварительной записи атрибутов в специально созданный для этой цели объект класса, реализующего Интерфейс Map: Класса HashMap, WeakHashMap или Hashtable (см. главу 7). Например: HashMap hm = new HashMap (); hm.put(TextAttribute.SIZE, new Float(60.Of)); hm.put(TextAttribute.POSTURE, TextAttribute.POSTUREJDBLIQUE); Font f = new Font(hm); Можно создать шрифт и вторым конструктором, которым мы пользовались в листинге 9.2, а потом добавлять и изменять атрибуты методами deriveFont () Класса Font. Текст в Java 2D обладает собственным контекстом — объектом класса FontRenderContext, хранящим всю информацию, необходимую для вывода текста. Получить его можно методом getFontRendercontext () класса Graphics2D. Вся информация о тексте, в том числе и об его контексте, собирается в объекте класса TextLayout. Этот класс в Java 2D заменяет класс FontMetrics. В конструкторе класса TextLayout задается текст, шрифт и контекст. Начало метода paint () со всеми этими определениями может выглядеть так: public void paint(Graphics gr){ Graphics2D g = (Graphics2D)gr; FontRenderContext frc = g.getFontRenderContex-t(); Font f = new Font("Serif", Font.BOLD, 15); String s = "Какой-то текст"; TextLayout tl = new TextLayout(s, f, frc) ; // Продолжение метода } В классе TextLayout есть не только более двадцати методов getxxxo, позволяющих узнать различные сведения о тексте, его шрифте и контексте, но и метод draw(Graphics2D g, float x, float у) вычерчивающий содержимое объекта класса TextLayout в графический области g, начиная с точки (х, у). Еще один интересный метод getOutline(AffineTransform at) возвращает контур шрифта в виде объекта shape. Этот контур можно затем заполнить по какому-нибудь образцу или вывести только контур, как показано в листинге 9.6. Листинг 9.6. Вывод текста средствами Java 20 import java.awt.*; import j ava.awt.font.*; import j ava.awt.geom.*; import java.awt.event.* class StillText extends Frame{ StillText(String s) { super(s); setSize(400, 200); setvisible(true); addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent ev){ System.exit(0) ; } }); } public void paint(Graphics gr){ Graphics2D g = (Graphics2D)gr; int w = getSize().width, h = getSize().height; FontRenderContext frc = g.getFontRenderContext(); String s = "Тень"; Font f = new Font("Serif", Font.BOLD, h/3); TextLayout tl = new TextLayout(s, f, frc); AffineTransform at = new AffineTransform(); at.setToTranslation(w/2-tl.getBounds().getwidth()/2, h/2); Shape sh = tl.getOutline(at); g.draw(sh); AffineTransform atsh = new AffineTransform(1, 0.0, 1.5, -1, 0.0, 0.0); g.transform(at); g.transform(atsh); Font df = f.deriveFont(atsh); TextLayout dtl = new TextLayout(s, df, frc); Shape sh2 = dtl.getOutline(atsh); g.fill(sh2); } public static void main(Stnng[] args) { new StillText(" Эффект тени"); } } На рис. 9.6 показан вывод этой программы.
Рис. 9.6. Вывод текста средствами Java 2D Еще одна возможность создать текст с атрибутами — определить объект класса Attributedstring из пакета j ava. text. Конструктор этого класса AttributedString(String text, Map attributes) задает сразу и текст, и его атрибуты. Затем можно добавить или изменить характеристики текста одним их трех методов addAttibute (). Если текст занимает несколько строк, то встает вопрос его форматирования. Для этого вместо класса TextLayout используется класс LineBreakMeasurer, методы которого позволяют отформатировать абзац. Для каждого сегмента текста можно получить экземпляр класса TextLayout и вывести текст, используя его атрибуты. Для редактирования текста необходимо отслеживать курсором (caret) текущую позицию в тексте. Это осуществляется методами класса TextHitinfo, а методы класса TextLayout позволяют получить позицию курсора, выделить блок текста" и подсветить его. Наконец, можно задать отдельные правила для вывода каждого символа текста. Для этого надо получить экземпляр класса Glyphvector методом createGiyphvector () класса Font, изменить позицию символа методом setciyphPosition(), задать преобразование символа, если это допустимо для данного шрифта, методом setciyphTransformo, и вывести измененный текст методом drawGiyphVector () класса Graphics2D. Все это показано в листинге 9.7 и на рис. 9.7 — выводе программы листинга 9.7.
Рис. 9.7. Вывод отдельных символов Листинг 9.7. Вывод отдельных символов import j ava.awt.*; import Java.awt.font.*; import java.awt.geom.*; import j ava.awt.event.*; class GlyphTest extends Frame{ GlyphTest(String s){ super(s) ; setSize(400, 150); setVisible(true); addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent ev){ System.exit(0); } }); } public void paint(Graphics gr){ int h = 5; Graphics2D g = (Graphics2D)gr; FontRenderContext frc = g.getFontRenderContext(); Font f = new Font("Serif", Font.BOLD, 30); GlyphVector gv = f.createGiyphvector(frc, "Пляшущий текст"); int len = gv.getNumGlyphs(); for (int i = 0; i < len; i++){ Point2D.Double p = new Point2D.Double(25 * i, h = -h); gv.setGlyphPosition(i, p) ; } g.drawGiyphVector(gv, 10, 100); } public static void main(String[] args)( new GlyphTest(" Вывод отдельных символов"); } } Визуализацию (rendering) созданной графики можно усовершенствовать, установив один из методов (hint) улучшения одним из методов класса Graphics2D: setRenderingHints(RenderingHints.Key key, Object value) setRenderingHints(Map hints) Ключи — методы улучшения — и их значения задаются константами класса RenderingHints, перечисленными в табл. 9.2. Таблица 9.2, Методы визуализации и их значения
He все графические системы обеспечивают выполнение этих методов, поэтому задание указанных атрибутов не означает, что определяемые ими методы будут применяться на самом деле. Вот как может выглядеть начало метода paint () с указанием методов улучшения визуализации: public void paint(Graphics gr){ Graphics2D g = (Graphics2D)gr; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); // Продолжение метода } В этой главе мы, разумеется, не смогли подробно разобрать все возможности Java 2D. Мы не коснулись моделей задания цвета и смешивания цветов, печати графики и текста, динамической загрузки шрифтов, изменения области рисования. В главе 15 мы рассмотрим средства Java 2D для работы с изображениями, в главе 18 — средства печати. В документации SUN J2SDK, в каталоге docs\guide\2d\spec, есть руководство Java 2 API Guide с обзором всех возможностей Java 2D. Там помещены ссылки на руководства и пособия по Java 2D. В каталоге demo\jfc\Java2D\src приведены исходные тексты программ, использующих Java 2D. |
|||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() |