Графические примитивы
При создании графического компонента, т. е. объекта класса Component, автоматически формируется его графический контекст (graphics context). В контексте размещается область рисования и вывода текста и изображений. Контекст содержит текущий и альтернативный цвет рисования и цвет фона — объекты класса Color, текущий шрифт для вывода текста — объект класса Font.
В контексте определена система координат, начало которой — точка с координатами (0, 0) — расположено в верхнем левом углу области рисования, ось Ox направлена вправо, ось Oy — вниз. Точки координатной системы находятся между точками области рисования.
Управляет контекстом класс Graphics или более новый класс Graphics2D, созданный в рамках библиотеки Java 2D. Поскольку графический контекст сильно зависит от конкретной графической платформы, эти классы сделаны абстрактными. Поэтому нельзя непосредственно создать экземпляры класса Graphics или Graphics2D.
Однако каждая виртуальная машина Java реализует методы этих классов, создает их экземпляры для компонента и предоставляет объект класса Graphics методом getGraphics ( ) класса Component или передает его как аргумент методов paint () и update ().
Посмотрим сначала, какие методы работы с графикой и текстом предоставляет нам класс Graphics.
Методы класса Graphics
При создании контекста в нем задается текущий цвет для рисования, обычно черный, и цвет фона области рисования — белый или серый. Изменить текущий цвет можно методом setColor (Color newColor), аргумент newColor которого — объект класса 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(0.05f, 0.4f, 0.95f);
Третий конструктор,
Color(int rgb);
задает все три составляющие в одном целом числе. В битах 16—23 записывается красная составляющая, в битах 8—15 — зеленая, а в битах 0—7 — синяя составляющая цвета. Например:
Color c = new Color(0xFF8F48AF);
Здесь красная составляющая задана с интенсивностью 0x8F, зеленая — 0x4 8, синяя —
0xAF .
Следующие три конструктора:
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, если параметр hasAlpha равен true. Если же hasAlpha равно false, то составляющая альфа считается равной 255, независимо от того, что записано в старших битах параметра rgb.
Можно сказать, что первые три конструктора создают непрозрачный цвет с альфой, равной 255 или 1.0.
Седьмой конструктор,
Color(ColorSpace cspace, float[] components, float alpha);
позволяет создавать цвет не только в цветовой модели (color model) RGB, но и в других моделях: CMYK, HSB, CIE XYZ, определенных объектом класса ColorSpace.
Для создания цвета в модели HSB можно воспользоваться статическим методом
getHSBColor(float hue, float saturation, float brightness);
Если нет необходимости тщательно подбирать цвета, то можно просто воспользоваться одной из тринадцати статических констант типа Color, имеющихся в классе Color. Вопреки соглашению "Code Conventions" в первых версиях JDK их записывали строчными буквами: black, blue, cyan, darkGray, gray, green, lightGray, magenta, orange, pink, red,
white, yellow. В последние версии JDK добавили те же константы, записанные прописными буквами: BLACK, blue, cyan, dark_gray, gray, green, light_gray, magenta, orange, pink, red, white, yellow. Сейчас можно использовать и ту и другую запись.
В классе SystemColor, расширяющем класс Color, собраны константы, выражающие системные цвета десктопа, окна, меню, текста, различных системных компонентов. С их помощью можно оформить приложение стандартными цветами графической оболочки операционной системы.
Методы класса Color позволяют получить составляющие текущего цвета: getRed(),
getGreen(), getBlue(), getAlpha(), getRGB(), getColorSpace(), getComponents().
Два метода создают более яркий brighter () и более темный darker () цвета по сравнению с текущим цветом. Они полезны, если надо выделить изображение активного компонента ярким цветом или, наоборот, показать неактивный компонент бледнее остальных компонентов.
Два статических метода возвращают цвет, преобразованный из цветовой модели RGB в модель HSB и обратно:
float[] RGBtoHSB(int red, int green, int blue, float[] hsb); int HSBtoRGB(int hue, int saturation, int brightness);
Создав цвет, можно рисовать им в графическом контексте.
Упражнение
1. Создайте чисто желтый цвет в разных цветовых моделях.
Как нарисовать чертеж
Основной метод рисования,
drawLine(int x1, int y1, int x2, int y2);
вычерчивает текущим цветом отрезок прямой между точками с координатами (xi, yi)
и (x2, y2).
Одного этого метода достаточно, чтобы нарисовать любую картину по точкам, вычерчивая каждую точку с координатами (x, у) методом drawLine(x, у, х, у) и меняя цвета от точки к точке. Но никто, разумеется, не станет этого делать.
Другие графические примитивы:
? drawRect (int x, int y, int width, int height) -чертит прямоугольник со сторонами,
параллельными краям экрана, задаваемый координатами верхнего левого угла (x, y), шириной width пикселов и высотой height пикселов;
? draw3DRect(int x, int y, int width, int height, boolean raised) — чертит прямоугольник, как будто выделяющийся из плоскости рисования, если параметр raised равен true, или как будто вдавленный в плоскость, если параметр raised равен false;
? drawOval(int x, int y, int width, int height) - чертит овал, вписанный в прямо
угольник, заданный параметрами метода. Если width == height, то получится окружность;
? drawArc(int x, int y, int width, int height, int startAngle, int arc) — чертит дугу овала, вписанного в прямоугольник, заданный первыми четырьмя параметрами. Дуга имеет величину arc градусов и отсчитывается от угла startAngle. Угол отсчитывается в градусах от оси Ox. Положительный угол отсчитывается против часовой стрелки, отрицательный — по часовой стрелке;
? drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) — чертит прямоугольник с закругленными краями. Закругления рисуются четвертинками овалов, вписанных в прямоугольники шириной arcWidth и высотой arcHeight, построенные в углах основного прямоугольника;
? drawPolyline (int [ ] xPoints, int[] yPoints, int nPoints) — чертит ломаную с вершинами в точках (xPoints [i] , yPoints [i] ) и числом вершин nPoints;
? drawPolygon (int [ ] xPoints, int[] yPoints, int nPoints) — чертит замкнутую ломаную, проводя замыкающий отрезок прямой между первой и последней точкой;
? drawPolygon (Polygon p) — чертит замкнутую ломаную, вершины которой заданы объектом p класса Polygon.
Класс Polygon рассмотрим подробнее.
Класс Polygon
Этот класс предназначен для работы с многоугольником, в частности, с треугольниками и произвольными четырехугольниками.
Объекты этого класса можно создать двумя конструкторами:
? Polygon () — создает пустой объект;
? Polygon (int [ ] xPoints, int[] yPoints, int nPoints) — задаются координаты вершин многоугольника (xPoints[i], yPoints[i]) и их число nPoints. Несоответствие параметров вызывает исключительную ситуацию.
После создания объекта в него можно добавлять вершины методом
addPoint(int x, int y);
Логические методы contains () позволяют проверить, не лежит ли в многоугольнике заданная параметрами метода точка, отрезок прямой или целый прямоугольник со сторонами, параллельными сторонам экрана:
boolean contains(int x, int y); boolean contains(double x, double y); boolean contains(Point p); boolean contains(Point2D p);
boolean contains(double x, double y, double width, double height); boolean contains(Rectangle2D rectangle);
Два логических метода intersects () позволяют проверить, не пересекается ли с данным многоугольником отрезок прямой, заданный параметрами метода, или прямоугольник со сторонами, параллельными сторонам экрана:
boolean intersects(double x, double y, double width, double height); boolean intersects(Rectangle2D rectangle);
Методы getBounds () и getBounds2D() возвращают прямоугольники класса Rectangle и класса Rectangle2D соответственно, целиком содержащие в себе данный многоугольник.
Упражнение
2. Сделайте рисунок по описанию: "Точка, точка, запятая, минус — рожица кривая".
Прочие методы класса Graphics
Вернемся к методам класса Graphics. Несколько методов вычерчивают фигуры, залитые текущим цветом: fillRect(), fill3DRect (), fillArc(), fillOval(), fillPolygon(), fillRoundRect (). У них такие же параметры, как и у соответствующих методов, вычерчивающих незаполненные фигуры.
Например, если вы хотите изменить цвет фона области рисования, то установите новый текущий цвет и начертите им заполненный прямоугольник величиной во всю область:
public void paint(Graphics g){
Color initColor = g.getColor(); // Сохраняем исходный цвет.
g.setColor(new Color(0, 0, 255)); // Устанавливаем цвет фона.
// Заливаем область рисования.
g.fillRect(0, 0, getSize().width — 1, getSize().height — 1); g.setColor(initColor); // Восстанавливаем исходный цвет.
// Дальнейшие действия...
}
Как видите, в классе Graphics собраны только самые необходимые средства рисования. Нет даже метода, задающего цвет фона (хотя можно указать цвет фона компонента методом setBackground () класса Component). Средства рисования, вывода текста в область рисования и вывода изображений значительно дополнены и расширены в подклассе Graphics2D, входящем в систему Java 2D. Например, в нем есть метод задания цвета фона setBackground(Color c).
Перед тем как обратиться к классу Graphics2D, рассмотрим средства класса Graphics для вывода текста.
Как вывести текст
Для вывода текста в область рисования текущим цветом и шрифтом, начиная с точки (x, y), в классе Graphics есть несколько методов:
? drawString(String s, int x, int y) — выводит строку s;
? drawBytes(byte[] b, int offset, int length, int x, int y) — выводит length элементов массива байтов b, начиная с индекса offset;
? drawChars(char[] ch, int offset, int length, int x, int y) — выводит length элементов массива символов ch, начиная с индекса offset.
Четвертый метод выводит текст, занесенный в объект класса, реализующего интерфейс AttributedCharacteriterator. Это позволяет задавать свой шрифт для каждого выводимого символа:
drawString(AttributedCharacterIterator iter, int x, int y);
Во всех этих методах точка (x, y) — это левая нижняя точка первой буквы текста на базовой линии (baseline) вывода шрифта.
Как установить шрифт
Метод setFont (Font newFont) класса Graphics устанавливает текущий шрифт для вывода текста.
Метод getFont () возвращает текущий шрифт.
Как и все в языке Java, шрифт — это объект, а именно объект класса Font. Посмотрим, какие возможности предоставляет данный класс.
Как задать шрифт
Объекты класса Font хранят начертания (glyphs) символов, образующие шрифт. Их можно создать двумя конструкторами:
? Font (Map attributes ) -задает шрифт с указанным аргументом attributes атрибутами.
Ключи атрибутов и некоторые их значения задаются константами класса TextAttribute из пакета java.awt.font. Этот конструктор характерен для Java 2D и будет рассмотрен далее в настоящей главе;
? Font(String name, int style, int size) — задает шрифт по имени name, со стилем style и размером size типографских пунктов. Этот конструктор характерен для JDK 1.1, но широко используется и в Java 2D в силу своей простоты.
Типографский пункт в России и некоторых европейских странах равен 0,376 мм, точнее, 1/72 части французского дюйма. В англо-американской системе мер пункт равен 1/72 части английского дюйма — 0,351 мм. Этот-то пункт и применяется в компьютерной графике.
Именем шрифта name может быть строка с физическим именем шрифта, например
"Courier New", или одна из строк "Dialog", "Dialoginput", "Monospaced", "Serif", "SansSerif", "Symbol". Это так называемые логические имена шрифтов (logical font names). Если name == null, то задается шрифт по умолчанию.
Стиль шрифта style — это одна из констант класса Font:
? bold — полужирный;
? ITALIC — курсив;
? PLAIN-обычный.
Полужирный курсив (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[] fonts = Toolkit.getGraphicsEnvironment.getAllFonts(); for (Font f: fonts)
System.out.println(f.getFontName());
В состав Java SE входит семейство шрифтов Lucida. Установив JDK, вы можете быть уверены, что эти шрифты есть в вашей системе.
Таблицы сопоставления логических и физических имен шрифтов находятся в виртуальной машине Java или в файлах с именами:
? fonteonfig.properties; ? fonteonfig.2003.properties;
? fontconfig.Me.properties; ? fontconfig.RedHat.properties
? fontconfig.2000.XP.properties; и т. д.
? fontconfig.XP.properties;
Эти файлы должны быть расположены в JDK в каталоге jdk1.7.0/jre/lib или каком-либо другом подкаталоге lib корневого каталога JDK той машины, на которой выполняется приложение.
Файлы хранятся в исходном виде, с расширением src, и в откомпилированном виде, с расширением bfc.
Нужный файл выбирается виртуальной машиной Java по названию операционной системы. Если такой файл не найден, то применяется файл fonteonfig.properties, не соответствующий никакой конкретной операционной системе.
Поэтому можно оставить в системе только один файл fontconfig.properties, переписав в него содержимое нужного файла или создав файл заново. Для любой операционной системы будет использоваться именно он.
В листинге 9.1 показано сокращенное содержимое файла fontconfig.properties.src из Java SE 7 для платформы MS Windows.
Листинг 9.1. Примерный файл fontconfig.properties.src
#
# Copyright © 2003, 2010, Oracle and/or its affilates. All rights reserved.
#
# Version version=1
# Component Font Mappings allfonts.chinese-ms936=SimSun allfonts.chinese-gb18030=SimSun-18030 allfonts.chinese-hkscs=MingLiU HKSCS allfonts.devanagari=Mangal allfonts.dingbats=Wingdings allfonts.lucida=Lucida Sans Regular allfonts.symbol=Symbol
allfonts.thai=Lucida Sans Regular
serif.plain.alphabetic=Times New Roman serif.plain.chinese-ms950=MingLiU serif.plain.hebrew=David serif.plain.japanese=MS Mincho serif.plain.korean=Batang
serif.bold.alphabetic=Times New Roman Bold
# И так далее
serif.italic.alphabetic=Times New Roman Italic
# И так далее
serif.bolditalic.alphabetic=Times New Roman Bold Italic
# И так далее
sansserif.plain.alphabetic=Arial
# И так далее
monospaced.plain.alphabetic=Courier New
# И так далее
dialog.plain.alphabetic=Arial
# И так далее
dialoginput.plain.alphabetic=Courier New
# И так далее
# Search Sequences
sequence.allfonts=alphabetic/default,dingbats,symbol
# И так далее
# Exclusion Ranges
exclusion.alphabetic=0700-1e9f,1f00-20ab,20ad-f8ff exclusion.chinese-gb18030=0390-03d6,2200-22ef,2701-27be exclusion.hebrew=0041-005a,0060-007a,007f-00ff,20ac-20ac
# Monospaced to Proportional width variant mapping
# (Experimental private syntax) proportional.MS Gothic=MS PGothic proportional.MS Mincho=MS PMincho proportional.MingLiU=PMingLiU
# Font File Names 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=TIMESBD.TTF
filename.Times New Roman Italic=TIMESI.TTF
filename.Times New Roman Bold Italic=TIMESBI.TTF
filename.SimSun=SIMSUN.TTC
filename.SimSun-18030=SIMSUN18030.TTC
filename.MingLiU=MINGLIU.TTC
filename.PMingLiU=MINGLIU.TTC
filename.MingLiU HKSCS=hkscsm3u.ttf
filename.David=DAVID.TTF
filename.David Bold=DAVIDBD.TTF
filename.MS_Mincho=MSMINCHO.TTC
filename.MS_PMincho=MSMINCHO.TTC
filename.MS_Gothic=MSGOTHIC.TTC
filename.MS_PGothic=MSGOTHIC.TTC
filename.Gulim=gulim.TTC
filename.Batang=batang.TTC
filename.GulimChe=gulim.TTC
filename.Lucida Sans Regular=LucidaSansRegular.ttf filename.Mangal=MANGAL.TTF filename.Symbol=SYMBOL.TTF filename.Wingdings=WINGDING.TTF
Большая часть этого файла занята сопоставлениями логических и физических имен. Вы видите, что:
? логическому имени "dialog" сопоставлено имя семейства "Arial";
? логическому имени "dialoginput" сопоставлено имя семейства "Courier New";
? логическому имени "serif" сопоставлено имя семейства "Times New Roman";
? логическому имени "sansserif" сопоставлено имя семейства "Arial";
? логическому имени "monospaced" сопоставлено имя семейства "Courier New".
Там, где указан стиль: dialog.italic, dialog.bold и т. д., подставлен соответствующий физический шрифт.
В строках листинга 9.1, начинающихся со слова filename, указаны файлы с соответствующими физическими шрифтами, например:
filename.Arial=ARIAL.TTF
Эти строки необязательны, но они ускоряют поиск файлов со шрифтами.
Теперь посмотрите на другие строки листинга 9.1. Строка
exclusion.alphabetic=0700-1e9f,1f00-20ab,20ad-f8ff
означает, что в алфавитных шрифтах не станут отыскиваться начертания (glyphs) символов с кодами в диапазонах 'u07 00' — 'u1e9f', 'u1f00 ' —' u20ab' и ' u20ad' —' uf8ff'.
Они будут взяты из шрифта, следующего далее в строке
sequence.allfonts=alphabetic/default,dingbats,symbol
а именно из шрифта Wingdings.
Итак, собираясь выводить строку str в графический контекст методом drawString( ), мы создаем текущий шрифт конструктором класса Font, указывая в нем логическое имя шрифта, например "Serif". Исполняющая система Java отыскивает в файле fonteonfig.properties, соответствующем локальному языку, сопоставленный этому логическому имени физический шрифт операционной системы, например Times New Roman. Если это Unicode-шрифт, то из него извлекаются начертания символов строки str по их кодировке Unicode и отображаются в графический контекст. Если это байтовый ASCII-шрифт, то строка str предварительно перекодируется в массив байтов методами соответствующего класса, например класса CharToByteCp1251.
Эти вопросы обсуждаются в документации Java SE в файле docs/technotes/guides/intl/ fontconfig.html.
Завершая рассмотрение логических и физических имен шрифтов, следует сказать, что в JDK 1.0 использовались логические имена "Helvetica", "TimesRoman", "Courier", из лицензионных соображений замененные в JDK 1.1 на "SansSerif", "Serif", "Monospaced" соответственно.
При выводе строки в окно приложения очень часто возникает необходимость расположить ее определенным образом относительно других элементов изображения: центрировать, вывести над или под другим графическим объектом. Для этого надо знать метрику строки: ее высоту и ширину. Для измерения размеров отдельных символов и строки в целом разработан класс FontMetrics.
В Java 2D класс FontMetrics заменен классом TextLayout. Его мы рассмотрим в конце этой главы, а сейчас выясним, какую пользу можно извлечь из методов класса
FontMetrics.
Класс FontMetrics
Класс FontMetrics является абстрактным, поэтому нельзя воспользоваться его конструктором. Для получения объекта класса FontMetrics, содержащего набор метрических характеристик шрифта f, следует обратиться к методу getFontMetrics(Font f) класса Graphics или класса Component.
Подробно с характеристиками компьютерных шрифтов можно познакомиться по книге [12].
Класс FontMetrics позволяет узнать ширину отдельного символа ch в пикселах методом charWidth(ch), общую ширину всех символов массива или подмассива символов или байтов — методами getchars () и getBytes (), ширину целой строки str в пикселах — методом stringWidth(str).
Несколько методов возвращают в пикселах вертикальные размеры шрифта.
Интерлиньяж (leading) — расстояние между нижней точкой свисающих элементов таких букв, как р, у, и верхней точкой выступающих элементов таких букв, как б, й, в следующей строке — возвращает метод getLeading ().
Среднее расстояние от базовой линии шрифта до верхней точки прописных букв и выступающих элементов той же строки (ascent) возвращает метод getAscent(), а максимальное — метод getMaxAscent ( ).
Среднее расстояние свисающих элементов от базовой линии той же строки (descent) возвращает метод getDescent (), а максимальное — метод getMaxDescent ().
Наконец, высоту шрифта (height) — сумму ascent + descent + leading — возвращает метод getHeight (). Высота шрифта равна расстоянию между базовыми линиями соседних строк.
Эти элементы показаны на рис. 9.1.
Абвдо f ascent Height __J__descent leading Жфйёь Ц baselineРис. 9.1. Элементы шрифта
Дополнительные характеристики шрифта можно определить методами класса LineMetrics из пакета java.awt.font. Объект этого класса можно получить несколькими методами getLineMetrics ( ) класса FontMetrics.
Листинг 9.2 показывает применение графических примитивов и шрифтов, а рис. 9.2 — результат выполнения программы из этого листинга.
Листинг 9.2. Использование графических примитивов и шрифтов
import java.awt.*; import javax.swing.*;
class GraphTest extends JFrame{
GraphTest(String s){ super(s);
setBounds(0, 0, 500, 300); setVisible(true);
}
public void paint(Graphics g){
Dimension d = getSize();
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.fillArc(d.width / 2 — dx, d.height — 2 * dy + 1,
2 * dx, dy — 1, 0, 360);
g.drawArc(d.width / 2 — 3 * dx, d.height — 3 * dy / 2 — 5, dx, dy / 2, 0, 360);
g.drawArc(d.width / 2 + 2 * dx, d.height — 3 * dy / 2 — 5, dx, dy / 2, 0, 360);
Font f1 = new Font("Serif", Font.BOLD|Font.ITALIC, 2 * dy);
Font f2 = new Font("Serif", Font.BOLD, 5 * dy / 2);
FontMetrics fm1 = getFontMetrics(f1);
FontMetrics fm2 = getFontMetrics(f2);
String si = "Всякая последняя ошибка";
String s2 = "является предпоследней.";
String s3 = "Закон отладки";
int firstLine = d.height / 3;
int nextLine = fm1.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(f1);
g.drawString(s1, (d.width — fm1.stringWidth(s1)) / 2, firstLine + 2 * nextLine);
g.drawString(s2, (d.width — fm1.stringWidth(s2)) / 2, firstLine + 3 * nextLine);
}
public static void main(String[] args){
GraphTest f = new GraphTest(" Пример рисования"); f.setDefaultCloseOperation(EXIT ON CLOSE);
}
}
Рис. 9.2. Пример использования класса GraphicsВ листинге 9.2 использован простой класс Dimension, главная задача которого — хранить ширину и высоту прямоугольного объекта в своих полях width и height. Метод getSize () класса Component возвращает размеры компонента в виде объекта класса Dimension. В ЛИСТИНГе 9.2 размеры компонента f типа GraphTest установлены в конструкторе методом setBounds () равными 500x300 пикселов.
Еще одна особенность листинга 9.2 — для вычерчивания толстой линии, отделяющей заголовок от текста, пришлось провести три параллельные прямые на расстоянии один пиксел друг от друга.
Как вы увидели из обзора класса Graphics и сопутствующих ему классов, средства рисования и вывода текста в этом классе весьма ограниченны. Линии можно проводить только сплошные и только толщиной в один пиксел, текст выводится только горизонтально и слева направо, не учитываются особенности устройства вывода, например разрешение экрана.
Эти ограничения можно обойти разными хитростями: чертить несколько параллельных линий, прижатых друг к другу, как в листинге 9.2, или узкий заполненный прямоугольник, выводить текст по одной букве, получить разрешение экрана методом getScreenSize ( ) класса java.awt.Toolkit и использовать его в дальнейшем. Но все это затрудняет программирование, лишает его стройности и естественности, нарушает принцип KISS.
Класс Graphics, в рамках системы Java 2D, значительно расширен классом Graphics2D.
Упражнение
3. Поупражняйтесь в выводе текста различными шрифтами с разным расположением на экране.
Возможности Java 2D
В систему пакетов и классов Java 2D, основа которой — класс Graphics2D пакета j ava. awt, внесено несколько принципиально новых положений.
? Кроме координатной системы, принятой в классе Graphics и названной координатным пространством пользователя (User Space), введена еще система координат устройства вывода (Device Space): экрана монитора, принтера. Методы класса Graphics2D автоматически переводят (transform) систему координат пользователя в систему координат устройства при выводе графики.
? Преобразование координат пользователя в координаты устройства можно задать "вручную", причем преобразованием способно служить любое аффинное преобразование плоскости, в частности поворот на любой угол и/или сжатие/растяжение. Оно определяется как объект класса AffineTransform. Его можно установить как преобразование по умолчанию методом setTransform(). Возможно выполнять преобразование "на лету" методами transform() и translate () и делать композицию преобразований методом concatenate ().
? Поскольку аффинное преобразование вещественно, координаты задаются вещественными, а не целыми числами.
? Графические примитивы: прямоугольник, овал, дуга и др., реализуют теперь новый интерфейс Shape пакета java.awt. Для их вычерчивания можно использовать новый единый для всех фигур метод draw (), аргументом которого способен служить любой объект, реализовавший интерфейс Shape. Введен метод fill (), заполняющий фигуры — объекты класса, реализовавшего интерфейс Shape.
? Для вычерчивания (stroke) линий введено понятие пера (pen). Свойства пера описывает интерфейс Stroke. Класс BasicStroke реализует этот интерфейс. Перо обладает четырьмя характеристиками:
• оно имеет толщину (width) в один (по умолчанию) или несколько пикселов;
• оно может закончить линию (end cap) закруглением — это статическая константа cap_round, прямым обрезом — константа cap_square (по умолчанию) или не фиксировать определенный способ окончания — константа cap_butt;
• оно может сопрягать линии (line joins) закруглением — статическая константа join_round, отрезком прямой — константа join_bevel или просто состыковывать — константа join_miter (по умолчанию);
• оно может чертить линию различными пунктирами (dash) и штрихпунктирами, при этом длины штрихов и промежутков задаются в массиве, элементы которого с четными индексами задают длину штриха, а с нечетными индексами — длину промежутка между штрихами.
? Методы заполнения фигур описаны в интерфейсе Paint. Несколько классов реализуют этот интерфейс. Класс Color реализует его сплошной (solid) заливкой, класс GradientPaint — градиентным (gradient) заполнением, при котором цвет плавно меняется от одной заданной точки к другой заданной точке, класс TexturePaint — заполнением по предварительно заданному образцу (pattern fill). Класс MultipleGradientPaint организует градиентную заливку с несколькими градиентами, причем его подкласс LinearGradientPaint делает линейную заливку, а подкласс RadialGradientPaint — радиальную заливку.
? Буквы текста понимаются как фигуры, т. е. объекты, реализующие интерфейс Shape, и могут вычерчиваться методом draw () с использованием всех возможностей этого метода. При их вычерчивании применяется перо, все методы заполнения фигур и их преобразования.
? Кроме имени, стиля и размера шрифт получил много дополнительных атрибутов, например преобразование координат, подчеркивание или перечеркивание текста, вывод текста справа налево. Цвет текста и его фона являются теперь атрибутами самого текста, а не графического контекста. Можно задать разную ширину символов шрифта, надстрочные и подстрочные индексы. Атрибуты устанавливаются константами класса TextAttribute.
? Процесс визуализации (rendering) регулируется правилами (hints), определенными константами класса RenderingHints.
С такими возможностями Java 2D стала полноценной системой рисования, вывода текста и изображений. Посмотрим, как реализованы эти возможности и как ими можно
воспользоваться.
Преобразование координат
Правило преобразования координат пользователя в координаты графического устройства (transform) задается автоматически при создании графического контекста так же, как цвет и шрифт. В дальнейшем его можно изменить методом setTransform() так же, как меняется цвет или шрифт. Параметром этого метода служит объект класса AffineTransform из пакета java.awt.geom, подобно объектам класса Color или Font при задании цвета или шрифта.
Рассмотрим подробнее класс AffineTransform.
Класс AffineTransform
Аффинное преобразование координат задается двумя основными конструкторами класса AffineTransform:
AffineTransform(double a, double b, double c, double d, double e, double f); AffineTransform(float a, float b, float c, float d, float e, float f);
При этом точка с координатами (x, y) в пространстве пользователя перейдет в точку с координатами (a * x + c * y + e, b * x + d * y + f) в пространстве графического устройства.
Такое преобразование не искривляет плоскость — прямые линии переходят в прямые, углы между линиями сохраняются. Примерами аффинных преобразований служат повороты вокруг любой точки на любой угол, параллельные сдвиги, отражения от осей, сжатия и растяжения по осям.
Следующие два конструктора используют в качестве параметра массив из шести элементов-коэффициентов преобразования {a, b, c, d, e, f} или массив из четырех элементов {a, b, c, d}, если e = f = 0, составленный из таких же коэффициентов в том же порядке:
AffineTransform(double[] arr);
AffineTransform(float[] arr);
Пятый конструктор создает копию другого, уже имеющегося, объекта:
AffineTransform(AffineTransform at);
Шестой конструктор — конструктор по умолчанию — создает тождественное преобразование:
AffineTransform();
Эти конструкторы математически точны, но неудобны при задании конкретных преобразований. Попробуйте рассчитать коэффициенты поворота на 57° вокруг точки с координатами (20, 40) или сообразить, как будет преобразовано пространство пользователя после выполнения методов:
AffineTransform at =
new AffineTransform(-1.5, 4.45, -0.56, 34.7, 2.68, 0.01); g.setTransform(at);
Во многих случаях удобнее создать преобразование статическими методами, возвращающими объект класса AffineTransform.
? getRotateInstance (double angle) — возвращает поворот на угол angle, заданный в радианах, вокруг начала координат. Положительное направление поворота таково, что точки оси Ox поворачиваются в направлении к оси Oy. Если оси координат пользователя не менялись преобразованием отражения, то положительное значение angle задает поворот по часовой стрелке.
? getRotateInstance (double angle, double x, double y) — такой же поворот вокруг точки с координатами (x, y) .
? getRotateInstance (double vx, double vy) - поворот, заданный вектором с координа
тами (vx, vy). Эквивалентен методу getRotateInstance(Math.atan2(vx, vy)).
? getRotateInstance(double vx, double vy, double x, double y) — поворот вокруг точки с координатами (x, y), заданный вектором с координатами (vx, vy). Эквивалентен методу getRotateInstance(Math.atan2(vx, vy), x, y).
? getQuadrantRotatelnstance (int n) - поворот n раз на угол 90° вокруг начала коорди
нат. Эквивалентен методу getRotateInstance(n * Math.PI / 2.0).
? getQuadrantRotateInstance(int n, double x, double y) — поворот n раз на угол 90° вокруг точки с координатами (x, y). Эквивалентен методу getRotateInstance(n * Math.PI / 2.0, x, y).
? getScaleInstance (double sx, double sy) — изменяет масштаб по оси Ox в sx раз, по оси Oy — в sy раз.
? getShareInstance (double shx, double shy) — преобразует каждую точку (x, y) в точку (x + shx * y, shy * x + y).
? getTranslateInstance (double tx, double ty) — сдвигает каждую точку (x, y) в точку (x + tx, y + ty).
Метод 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); setToRotation(double vx, double vy);
setToRotation(double vx, double vy, double x, double y); setToQuadrantRotation(int n);
setToQuadrantRotation(int n, 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); rotate(double vx, double vy);
rotate(double vx, double vy, double x, double y); quadrantRotate(int n);
quadrantRotate(int n, 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, строку импорта
import java.awt.geom.*;
и четыре оператора, как записано в листинге 9.3.
Листинг 9.3. Преобразование пространства пользователя
// Начало листинга 9.2... public void paint(Graphics gr){
Graphics2D g = (Graphics2D)gr;
AffineTransform at =
AffineTransform.getRotateInstance(-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 () начинается с получения экземпляра g класса Graphics2D простым приведением аргумента gr к типу GraphicsiD. Затем. методом getRotateInstance () определяется поворот на 45° против часовой стрелки вокруг точки (250.0, 150.0). Это преобразование — экземпляр at класса AffineTransform. Метод concatenate (), выполняемый объектом at, добавляет к этому преобразованию сжатие в два раза по обеим осям координат и перенос начала координат в точку (100.0, 60.0). Наконец, композиция этих преобразований устанавливается как текущее преобразование объекта g методом setTrans form ().
Преобразование выполняется в следующем порядке. Сначала пространство пользователя сжимается в два раза вдоль обеих осей, затем начало координат пользователя — левый верхний угол — переносится в точку (100.0, 60.0) пространства графического устройства. Потом картинка поворачивается на угол 45° против часовой стрелки вокруг точки (250.0, 150.0).
Результат этих преобразований показан на рис. 9.3.
Рис. 9.3. Преобразование координатУпражнение
4. Напишите полную программу по листингу 9.3 и выполните ее несколько раз, меняя коэффициенты преобразований.
Рисование фигур средствами Java 2D
Характеристики пера для рисования фигур описаны в интерфейсе Stroke. В Java 2D есть пока только один класс, реализующий этот интерфейс, класс BasicStroke.
Класс BasicStroke
Конструкторы класса BasicStroke определяют характеристики пера. Основной конструктор,
BasicStroke(float width, int cap, int join, float miter, float[] dash, float dashBegin);
задает:
? толщину пера width в пикселах;
? оформление конца линии cap; это одна из констант:
• cap_round — закругленный конец линии;
• cap_square — квадратный конец линии;
• cap_butt — оформление отсутствует;
? способ сопряжения линий j oin; это одна из констант:
• join_round — линии сопрягаются дугой окружности;
• join_bevel — линии сопрягаются отрезком прямой, перпендикулярным биссектрисе угла между линиями;
• join_miter — линии просто стыкуются;
? расстояние между линиями miter, начиная с которого применяется сопряжение
join_miter;
? длину штрихов и промежутков между штрихами — массив dash; элементы массива с четными индексами задают длину штриха в пикселах, элементы с нечетными индексами — длину промежутка; массив перебирается циклически;
? индекс dashBegin, начиная с которого перебираются элементы массива dash. Остальные конструкторы задают некоторые характеристики по умолчанию:
? BasicStroke(float width, int cap, int join, float miter) — сплошная линия;
? BasicStroke (float width, int cap, int join) — сплошная линия с сопряжением JOIN_ROUND или JOIN_BEVEL; для сопряжения JOIN_MITER задается значение miter = 10. of;
? BasicStroke(float width) — прямой обрез cap_square и сопряжение join_miter со значением miter = 10.0f;
? BasicStroke () — ширина 1. of.
Лучше один раз увидеть, чем сто раз прочитать. В листинге 9.4 определены пять перьев с разными характеристиками, рис. 9.4 показывает, как они рисуют.
Листинг 9.4. Определение перьев
import j ava.awt.*; import java.awt.geom.*; import j avax.swing.*;
class StrokeTest extends JFrame{
StrokeTest(String s){ super(s);
setSize(500, 400); setVisible(true);
setDefaultCloseOperation(JFrame.EXIT ON CLOSE);
}
public void paint(Graphics gr){
Graphics2D g = (Graphics2D)gr; g.setFont(new Font("Serif", Font.PLAIN, 15));
BasicStroke pen1 = new BasicStroke(20, BasicStroke.CAP BUTT, BasicStroke.JOIN_MITER,30);
BasicStroke pen2 = new BasicStroke(20, BasicStroke.CAP ROUND, BasicStroke.JOIN_ROUND);
BasicStroke pen3 = new BasicStroke(20, BasicStroke.CAP SQUARE, BasicStroke.JOIN_BEVEL); float[] dash1 = {5, 20};
BasicStroke pen4 = new BasicStroke(10, BasicStroke.CAP ROUND, BasicStroke.JOIN_BEVEL, 10, dash1, 0);
float[] dash2 = {10, 5, 5, 5};
g. g. g. g. g. g. g. g. g. g. g. g. g. g. g. g. g. g. g. g. g.
}
public static void main(String[] args){ new StrokeTest(" Различные перья");
}
BasicStroke pen5 = new BasicStroke(10, BasicStroke.CAP BUTT, BasicStroke.JOIN_BEVEL, 10, dash2, 0); setStroke(pen1);
draw(new Rectangle2D.Double(50, 50, 50, 50)); draw(new Line2D.Double(50, 180, 150, 180)); setStroke(pen2);
draw(new Rectangle2D.Double(200, 50, 50, 50)); draw(new Line2D.Double(50, 230, 150, 230)); setStroke(pen3);
draw(new Rectangle2D.Double(350, 50, 50, 50)); draw(new Line2D.Double(50, 280, 150, 280)); drawString("JOIN_MITER", 40, 130);
drawStringC'JOIN ROUND" drawString("JOIN BEVEL" drawString("CAP_BUTT", drawString("CAP ROUND", drawString("CAP_SQUARE" setStroke(pen5); draw(new Line2D.Double( setStroke(pen4); draw(new Line2D.Double( drawString("{10, 5, 5, 5,...}
180, 130);
330, 130);
170, 190);
170, 240);
170, 290);
Рис. 9.4. Перья с различными характеристиками 330, 250, 330) ) 360, 250, 360) ) ..}", 260, 335); 260, 365) ;После создания пера ОДНИМ ИЗ конструкторов И установки пера методом 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 для вычерчивания прямоугольников и отрезков.
Класс GeneralPath
В пакете java.awt.geom есть еще один интересный класс — GeneralPath. Объекты этого класса могут содержать сложные конструкции, составленные из отрезков прямых или кривых линий и прочих фигур, соединенных или не соединенных между собой. Более того, поскольку этот класс реализует интерфейс Shape, его экземпляры сами являются фигурами и могут быть элементами других объектов класса GeneralPath.
Объект класса GeneralPath строится так. Вначале создается пустой объект класса GeneralPath конструктором по умолчанию GeneralPath() или объект, содержащий одну фигуру, конструктором GeneralPath (Shape sh).
Затем к этому объекту добавляются фигуры методом
append(Shape sh, boolean connect);
Если параметр connect равен true, то новая фигура соединяется с предыдущими фигурами с помощью текущего пера.
В объекте есть текущая точка. Вначале ее координаты (0, 0), затем ее можно переместить в точку (x, у) методом moveTo(float x, float y).
От текущей точки к точке (x, у) можно провести:
? отрезок прямой методом lineTo(float x, float у);
? отрезок квадратичной кривой методом quadTo(float x1, float у1, float x, float у);
? кривую Безье методом curveTo(float x1, float у1, float x2, float у2, float x, float у).
Текущей точкой после этого становится точка (x, у) . Начальную и конечную точки можно соединить методом closePath (). Вот как можно создать треугольник с заданными вершинами:
GeneralPath p = new GeneralPath();
p.moveTo(x1, y1); // Переносим текущую точку в первую вершину,
p.lineTo(x2, y2); // проводим сторону треугольника до второй вершины,
p.lineTo(x3, y3); // проводим вторую сторону,
p.closePath(); // проводим третью сторону до первой вершины.
Способы заполнения фигур определены в интерфейсе Paint. В настоящее время Java 2D
содержит несколько реализаций этого интерфейса- классы Color, GradientPaint,
TexturePaint, абстрактный класс MultipleGradientPaint и его расширения LinearGradientPaint и RadialGradientPaint. Класс Color нам известен, посмотрим, какие способы заливки предлагают классы GradientPaint и TexturePaint.
Классы GradientPaint и TexturePaint
Класс GradientPaint предлагает сделать заливку следующим образом.
В двух точках, м и N, устанавливаются разные цвета. В точке M(x1, у1) задается цвет c1, в точке N(x2, у2) — цвет c2. Цвет заливки гладко меняется от c1 к c2 вдоль прямой, соединяющей точки М и N, оставаясь постоянным вдоль каждой прямой, перпендикулярной прямой mn. Такую заливку создает конструктор
GradientPaint(float x1, float у1, Color c1, float x2, float у2, Color c2);
При этом вне отрезка mn цвет остается постоянным: за точкой м — цвет c1, за точкой N - цвет c2.
Второй конструктор,
GradientPaint(float x1, float у1, Color c1,
float x2, float у2, Color c2, boolean cyclic);
при задании параметра cyclic == true повторяет заливку полосы mn во всей заливаемой фигуре.
Еще два конструктора задают точки как объекты класса Point2D.
Класс TexturePaint поступает сложнее. Сначала создается буфер — объект класса BufferedImage из пакета java.awt.image. Это большой сложный класс. Мы с ним еще встретимся в главе 20, а пока нам понадобится только его графический контекст, управляемый экземпляром класса Graphics2D. Этот экземпляр можно получить методом createGraphics () класса BufferedImage. Графический контекст буфера заполняется фигурой, которая будет служить образцом заполнения.
Затем по буферу создается объект класса TexturePaint. При этом еще задается прямоугольник, размеры которого являются размерами образца заполнения. Конструктор выглядит так:
TexturePaint(BufferedImage 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 javax.swing.*;
class PaintTest extends JFrame{
PaintTest(String s){ super(s);
setSize(300, 300); setVisible(true);
setDefaultCloseOperation(JFrame.EXIT ON CLOSE);
}
public void paint(Graphics gr){
Graphics2D g = (Graphics2D)gr;
BufferedImage bi =
new BufferedImage(20, 20, BufferedImage.TYPE INT 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.fill(new Rectangle2D.Double(50,50, 200, 200)); GradientPaint gp =
new GradientPaint(100, 100, Color.white,
150, 150, Color.black, true);
g.setPaint(gp);
g.fill(new Ellipse2D.Double(100, 100, 200, 200));
}
public static void main(String[] args){ new PaintTest(" Способы заливки");
}
}
Рис. 9.5. Способы заливкиКлассы LinearGradientPaint и RadialGradientPaint
Классы LinearGradientPaint и RadialGradientPaint позволяют сделать градиентную заливку несколькими цветами, повторяя их вдоль прямой или радиально, вдоль радиуса окружности. Общие свойства этих классов собраны в абстрактном суперклассе
MultipleGradientPaint.
Так же, как и в классе GradientPaint, задается промежуток mn, но теперь промежуток делится на несколько частей точками с вещественными координатами от 0.0 — начало промежутка — до 1.0 — конец промежутка. Эти точки заносятся в массив, например:
float[] dist = {0.0f, 0.2f, 1.0f};
Здесь промежуток разделен на две неравные части: одна пятая часть и четыре пятых части.
В каждой точке деления промежутка задается свой цвет. Цвета тоже записываются в массиве:
Color[] color = {Color.RED, Color.WHITE, Color.BLUE};
В каждой части промежутка цвет плавно меняется от одного цвета к другому. В нашем примере на одной пятой части промежутка идет переход от красного к белому цвету, а на оставшихся четырех пятых — переход от белого к синему цвету.
После этого для линейной заливки создается экземпляр класса LinearGradientPaint:
LinearGradientPaint(float x1, float у1, float x2, float у2, float[] dist, Color[] color);
а для радиальной заливки — экземпляр класса RadialGradientPaint.
RadialGradientPaint(float x, float у, float radius, float[] dist, Color[] color);
Как вы поняли, для линейной заливки задаются координаты начальной M(x1, у1) и конечной N(x2, у2) точки промежутка, а для радиальной заливки — координаты центра круга A(x, у) и радиус окружности radius.
Способы задания цветов вне промежутка mn заданы константами вложенного класса MultipleGradientPaint. cycleMethod. В шестой версии Java SE есть три способа:
? no_cycle — используются первый и последний цвет (по умолчанию);
? reflect — перед промежутком циклически перебираются цвета от первого до последнего, а после промежутка — от последнего до первого;
? REFLECT — циклически перебираются цвета от первого до последнего.
Эти константы указываются в следующих конструкторах:
LinearGradientPaint(float x1, float у1, float x2, float у2, float[] dist, Color[] color,
MultipleGradientPaint.CycleMethod method);
RadialGradientPaint(float x, float у, float radius, float[] dist, Color[] color,
MultipleGradientPaint.CycleMethod method);
Еще несколько конструкторов задают точки как объекты класса Point2D.
По умолчанию оба класса используют пространство цветов RGB, но соответствующими конструкторами можно задать и другое пространство цветов.
После создания заливки — объекта класса LinearGradientPaint или RadialGradientPaint — она устанавливается в графическом контексте методом setPaint (Paint p) и используется в дальнейшем методом fill (Shape sh).
Вывод текста средствами Java 2D
Шрифт — объект класса Font — кроме имени, стиля и размера имеет еще полтора десятка атрибутов: подчеркивание, перечеркивание, наклон, цвет шрифта и цвет фона, ширину и толщину символов, аффинное преобразование, расположение слева направо или справа налево.
Атрибуты шрифта задаются как статические константы класса TextAttribute. Наиболее используемые атрибуты перечислены в табл. 9.1.
Таблица 9.1. Атрибуты шрифта Атрибут Значение BACKGROUND Цвет фона. Объект, реализующий интерфейс Paint FOREGROUND Цвет текста. Объект, реализующий интерфейс Paint BIDI EMBEDDED Уровень вложенности просмотра текста. Целое от 1 до 15 CHAR REPLACEMENT Фигура, заменяющая символ. Объект GraphicAttribute FAMILY Семейство шрифта. Строка типа String FONT Шрифт. Объект класса Font JUSTIFICATION Допуск при выравнивании абзаца. Объект класса Float со значениями от 0.0 до 1.0. Есть две константы: justification full и JUSTIFICATION NONE KERLING Керлинг — сдвиг букв в слове с целью уменьшения промежутков между ними, например в слове "AWAY". Константа KERLING ON LIGATURES Лигатура — слияние букв, например в слове "float". КонстантаLIGATURES ON POSTURE Наклон шрифта. Объект класса Float. Есть две константы:POSTURE OBLIQUE и POSTURE REGULAR RUN DIRECTION Просмотр текста: RUN DIRECTION LTR — слева направо,RUN DIRECTION RTL — справа налево SIZE Размер шрифта в пунктах. Объект класса Float STRIKETHROUGH Перечеркивание шрифта. Задается константой strikethrough ON, по умолчанию перечеркивания нет SUPERSCRIPT Подстрочные или надстрочные индексы. Константы: SUPERSCRIPT NONE, SUPERSCRIPT SUB, SUPERSCRIPT SUPER SWAP COLORS Замена местами цвета текста и цвета фона. Константа SWAP COLORS ON, по умолчанию замены нет Таблица 9.1 (окончание) Атрибут Значение TRAKING Трекинг — пропорциональное изменение расстояний между буквами. Константа TRAKING TIGHT увеличивает расстояния, а константаtraking loose уменьшает их TRANSFORM Преобразование шрифта. Объект класса AffineTransform UNDERLINE Подчеркивание шрифта. Константы: UNDERLINE ON,UNDERLINE LOW DASHED, UNDERLINE LOW DOTTED, UNDERLINE LOW GRAY, UNDERLINE LOW ONE PIXEL, UNDERLINE LOW TWO PIXEL WEIGHT Толщина шрифта. Константы: WEIGHT ULTRA LIGHT,WEIGHT EXTRA LIGHT, WEIGHT LIGHT, WEIGHT DEMILIGHT,WEIGHT REGULAR, WEIGHT SEMIBOLD, WEIGHT MEDIUM,WEIGHT DEMIBOLD, WEIGHT BOLD, WEIGHT HEAVY, WEIGHT EXTRABOLD, WEIGHT__ULTRABOLD WIDTH Ширина шрифта. Константы: width condensed, width semi condensed, WIDTH REGULAR, WIDTH SEMI EXTENDED, WIDTH EXTENDED К сожалению, не все шрифты позволяют задать все атрибуты. Посмотреть список допустимых атрибутов для данного шрифта можно методом getAvailableAttributes ( ) класса Font. Например:Font f = new Font("Times New Roman", Font.BOLD, 12);AttributedCharacterIterator.Attribute[] a = f.getAvailableAttributes(); for (int i = 0; i < a.length; i++)System.out.println(a[i]);В классе Font есть конструктор Font (Map attributes), которым можно сразу задать нужные атрибуты создаваемому шрифту. Это требует предварительной записи атрибутов в специально созданный для этой цели объект класса, реализующего интерфейс Map: класса HashMap, WeakHashMap или Hashtable (см. главу 7). Например:
HashMap hm = new HashMap();
hm.put(TextAttribute.SIZE, new Float(60.0f));
hm.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
Font f = new Font(hm);
Можно создать шрифт и вторым конструктором, которым мы пользовались в листинге 9.2, а потом добавлять и изменять атрибуты методами deriveFont ( ) класса Font.
Текст в Java 2D обладает собственным контекстом- объектом класса FontRenderContext,
хранящим всю информацию, необходимую для вывода текста. Получить его можно методом getFontRenderContext () класса Graphics2D.
Вся информация о тексте, в том числе и о его контексте, собирается в объекте класса TextLayout. Этот класс в Java 2D заменяет класс FontMetrics.
В конструкторе класса TextLa^out задается текст, шрифт и контекст. Начало метода paint () со всеми этими определениями может выглядеть так:
public void paint(Graphics gr){
Graphics2D g = (Graphics2D)gr;
FontRenderContext frc = g.getFontRenderContext();
Font f = new Font("Serif", Font.BOLD, 15);
String s = "Какой-то текст";
TextLayout tl = new TextLayout(s, f, frc);
// Продолжение метода
}
В классе TextLayout есть не только более двадцати методов getXXX(), позволяющих узнать различные сведения о тексте, его шрифте и контексте, но и метод
draw(Graphics2D g, float x, float y);
вычерчивающий содержимое объекта класса TextLayout в графической области g, начиная с точки (x, y) .
Еще один интересный метод,
getOutline(AffineTransform at);
возвращает контур шрифта в виде объекта Shape. Этот контур можно затем заполнить по какому-нибудь образцу или вывести только контур, как показано в листинге 9.6.
Листинг 9.6. Вывод текста средствами Java 2D
import java.awt.*; import java.awt.font.*; import java.awt.geom.*; import javax.swing.*;
class StillText extends JFrame{
StillText(String s){ super(s);
setSize(400, 200); setVisible(true);
setDefaultCloseOperation(JFrame.EXIT ON CLOSE);
}
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(String[] args){ new StillText(" Эффект тени");
}
}
На рис. 9.6 показан вывод этой программы.
Рис. 9.6. Вывод текста средствами Java 2DЕще одна возможность создать текст с атрибутами — определить объект класса Attributedstring из пакета java.text. Конструктор этого класса
AttributedString(String text, Map attributes);
задает сразу и текст, и его атрибуты. Затем можно добавить или изменить характеристики текста одним их трех методов addAttibute ( ).
Если текст занимает несколько строк, то встает вопрос его форматирования. Для этого вместо класса TextLayout применяется класс LineBreakMeasurer, методы которого позволяют отформатировать абзац. Для каждого сегмента текста можно получить экземпляр класса TextLayout и вывести текст, используя его атрибуты.
Для редактирования текста необходимо отслеживать курсором (caret) текущую позицию в тексте. Это осуществляется методами класса TextHitinfo, а методы класса TextLayout позволяют получить позицию курсора, выделить блок текста и подсветить его.
Наконец, можно задать отдельные правила для вывода каждого символа текста. Для этого надо получить экземпляр класса GlyphVector методом createGlyphVector ( ) класса Font, изменить позицию символа методом setGlyphPosition (), задать преобразование символа, если это допустимо для данного шрифта, методом setGlyphTransform(), и вывести измененный текст методом drawGlyphVector() класса Graphics2D. Все это показано в листинге 9.7 и на рис. 9.7 — выводе программы листинга 9.7.
Листинг 9.7. Вывод отдельных символов
import java.awt.*; import java.awt.font.*;
import java.awt.geom.*; import javax.swing.*;
class GlyphTest extends JFrame{
GlyphTest(String s){ super(s);
setSize(400, 150); setVisible(true);
setDefaultCloseOperation(JFrame.EXIT ON CLOSE);
}
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.createGlyphVector(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.drawGlyphVector(gv, 10, 100);
}
public static void main(String[] args){
new GlyphTest(" Вывод отдельных символов");
}
}
Рис. 9.7. Вывод отдельных символовМетоды улучшения визуализации
Визуализацию (rendering) созданной графики можно усовершенствовать, установив метод улучшения (hint) одним из методов класса Graphics2D:
setRenderingHints(RenderingHints.Key key, Object value); setRenderingHints(Map hints);
Ключи — методы улучшения — и их значения задаются константами класса RenderingHints, некоторые из них перечислены в табл. 9.2.
Таблица 9.2. Методы визуализации и их значения Метод Значение KEY ANTIALIASING Размывание крайних пикселов линий для гладкости изображения; три значения, задаваемые константами: VALUE ANTIALIAS DEFAULT, VALUE ANTIALIAS ON, VALUE ANTIALIAS OFF KEY TEXT ANTIALIASING То же для текста. Константы: VALUE TEXT ANTIALIASING DEFAULT, VALUE TEXT ANTIALIASING ON, VALUE TEXT ANTIALIASING OFF. Для LCD-мониторов константы: value text antialias gasp,VALUE TEXT ANTIALIAS LCD HRGB,VALUE TEXT ANTIALIAS LCD HBGR,VALUE TEXT ANTIALIAS LCD VRGB,VALUE TEXT ANTIALIAS LCD VBGR KEY RENDERING Три типа визуализации. Константы: VALUE RENDER SPEED, VALUE RENDER QUALITY, VALUE RENDER DEFAULT KEY COLOR RENDERING То же для цвета. Константы: VALUE COLOR RENDER SPEED, VALUE COLOR RENDER QUALITY, VALUE COLOR RENDER DEFAULT KEY ALPHA INTERPOLATION Плавное сопряжение линий. Константы:VALUE ALPHA INTERPOLATION SPEED, VALUE ALPHA INTERPOLATION QUALITY, VALUE ALPHA INTERPOLATION DEFAULT KEY INTERPOLATION Способы сопряжения. Константы: VALUE INTERPOLATION BILINEAR, VALUE INTERPOLATION BICUBIC,VALUE INTERPOLATION NEAREST NEIGHBOR KEY DITHERING Замена близких цветов. Константы: VALUE DITHER ENABLE, VALUE DITHER DISABLE, VALUE DITHER DEFAULT KEY ALPHA INTERPOLATION Способ альфа-интерполяции. Константы:VALUE ALPHA INTERPOLATION DEFAULT, VALUE ALPHA INTERPOLATION QUALITY, VALUE ALPHA INTERPOLATION SPEED KEY STROKE CONTROL Способ рисования. Константы: VALUE STROKE DEFAULT, VALUE STROKE NORMALIZE, VALUE STROKE PUREНе все графические системы обеспечивают выполнение этих методов, поэтому задание указанных атрибутов не означает, что определяемые ими методы будут применяться на самом деле.
Вот как может выглядеть начало метода 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);
// Продолжение метода
Для того чтобы как можно лучше связать визуализацию с настройками дисплея, определено свойство "awt.font.desktophints", хранящее таблицу типа Map с методами улучшения визуализации, имеющимися в настройках дисплея. Воспользоваться этим свойством можно так:
public void paint(Graphics gr){
Graphics2D g = (Graphics2D)gr;
Toolkit tk = Toolkit.getDefaultToolkit();
Map map = (Map)(tk.getDesktopProperty("awt.font.desktophints")); if (map != null) g.addRenderingHints(map);
// Продолжение метода
}
Упражнение
5. Перепишите предыдущие упражнения средствами Java 2D.
Заключение
В этой главе мы, разумеется, не смогли подробно разобрать все возможности Java 2D. Мы не коснулись моделей задания цвета и смешивания цветов, печати графики и текста, динамической загрузки шрифтов, изменения области рисования. В главе 20 будут рассмотрены средства Java 2D для работы с изображениями, в главе 23 — средства печати.
В документации Java SE, в каталоге docs/technotes/guides/2d/spec, есть руководство Programmer's Guide to the Java 2D API с обзором всех возможностей Java 2D. Там помещены ссылки на руководства и пособия по Java 2D. В каталоге demo/jfc/Java2D/ приведена демонстрационная программа и исходные тексты программ, использующих Java 2D.
Вопросы для самопроверки
1. Что такое цвет в библиотеке AWT?
2. Что такое шрифт в библиотеке AWT?
3. Что такое графический контекст?
4. Как нарисовать треугольник?
5. Как нарисовать окружность?
6. Как преобразовать чертеж: повернуть его, уменьшить или увеличить?
7. Можно ли писать текст сверху вниз или справа налево?
ГЛАВА 10