Рис. 1. Элементы шрифта
Дополнительные характеристики шрифта можно определить методами класса LineMetrics из пакета java.awt.font. Объект этого класса можно получить несколькими методами getLineMetrics () класса FontMetrics.
Пример 1 показывает применение графических примитивов и шрифтов, а рис. 2 — результат выполнения программы из этого примера.
Пример 1. Использование графических примитивов и шрифтов
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 = 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.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(s1))/2, 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);
}
});
}
}
В примере 1 использован простой класс Dimension, главная задача которого — хранить ширину и высоту прямоугольного объекта в своих полях width и height. Метод getSize() класса component возвращает размеры компонента в виде объекта класса Dimension. В программе 1 размеры компонента f типа GrapTest установлены в конструкторе методом setBounds() равными 500x300 пикселов.
Еще одна особенность примера 1 — для вычерчивания толстой линии, отделяющей заголовок от текста, пришлось провести три параллельные прямые на расстоянии один пиксел друг от друга.
Рис. 2. Пример использования класса Graphics
Как вы увидели из обзора класса Graphics и сопутствующих ему классов, средства рисования и вывода текста в этом классе весьма ограничены. Линии можно проводить только сплошные и только толщиной в один пиксел, текст выводится только горизонтально и слева направо, не учитываются особенности устройства вывода, например, разрешение экрана.
Эти ограничения можно обойти разными хитростями: чертить несколько параллельных линий, прижатых друг к другу, как в примере 1, или узкий заполненный прямоугольник, выводить текст по одной букве, получить разрешение экрана методом getScreenSize() класса Java.awt.Toolkit и использовать его в дальнейшем. Но все это затрудняет программирование, лишает его стройности и естественности.
В Java 2 класс Graphics, в рамках системы Java 2D, значительно расширен классом Graphics2D.
В систему пакетов и классов Java 2D, основа которой— класс Graphics2D пакета java.awt, внесено несколько принципиально новых положений:
· Кроме координатной системы, принятой в классе Graphics и названной координатным пространством пользователя (User Space), введена еще система координат устройства вывода (Device Space): экрана монитора, принтера. Методы класса Graphics2D автоматически переводят (transform) систему координат пользователя в систему координат устройства при выводе графики.
· Преобразование координат пользователя в координаты устройства можно задать "вручную", причем преобразованием способно служить любое аффинное преобразование плоскости, в частности, поворот на любой угол и/или сжатие/растяжение. Оно определяется как объект класса AffineTransform. Его можно установить как преобразование по умолчанию методом setTransform(). Возможно выполнять преобразование "на лету" методами transform() и translate() и делать композицию преобразований методом concatenate().
· Поскольку аффинное преобразование вещественно, координаты задаются вещественными, а не целыми числами.
· Графические примитивы: прямоугольник, овал, дуга и др., реализуют теперь новый интерфейс shape пакета java.awt. Для их вычерчивания можно использовать новый единый для всех фигур метод draw(), аргументом которого способен служить любой объект, реализовавший интерфейс shape. Введен метод fill(), заполняющий фигуры— объекты класса, реализовавшего интерфейс shape.
· Для вычерчивания (Stroke) линий введено понятие пера (реn). Свойства пера описывает интерфейс Stroke. Класс BasicStroke реализует этот интерфейс. Перо обладает четырьмя характеристиками:
o оно имеет толщину (width) в один (по умолчанию) или несколько пикселов;
o оно может закончить линию (end cap) закруглением — статическая константа CAP_ROUND, прямым обрезом — CAP_SQUARE (по умолчанию), или не фиксировать определенный способ окончания — CAP_BUTT;
o оно может сопрягать линии (line joins) закруглением — статическая константа JOIN_ROOND, отрезком прямой — JOIN_BEVEL, или просто состыковывать — JOIN_MITER (по умолчанию);
o оно может чертить линию различными пунктирами (dash) и штрих-пунктирами, длины штрихов и промежутков задаются в массиве, элементы массива с четными индексами задают длину штриха, с нечетными индексами — длину промежутка между штрихами.
· Методы заполнения фигур описаны в интерфейсе Paint. Три класса реализуют этот интерфейс. Класс Color реализует его сплошной (solid) заливкой, класс GradientPaint — градиентным (gradient) заполнением, при котором цвет плавно меняется от одной заданной точки к другой заданной точке, класс TexturePaint — заполнением по предварительно заданному образцу (pattern fill).
· Буквы текста понимаются как фигуры, т. е. объекты, реализующие интерфейс shape, и могут вычерчиваться методом draw() с использованием всех возможностей этого метода. При их вычерчивании применяется перо, все методы заполнения и преобразования.
· Кроме имени, стиля и размера, шрифт получил много дополнительных атрибутов, например, преобразование координат, подчеркивание или перечеркивание текста, вывод текста справа налево. Цвет текста и его фона являются теперь атрибутами самого текста, а не графического контекста. Можно задать разную ширину символов шрифта, надстрочные и подстрочные индексы. Атрибуты устанавливаются константами класса TextAttribute.
· Процесс визуализации (rendering) регулируется правилами (hints), определенными константами класса RenderingHints.
С такими возможностями Java 2D стала полноценной системой рисования, вывода текста и изображений. Посмотрим, как реализованы эти возможности, и как ими можно воспользоваться.
Правило преобразования координат пользователя в координаты графического устройства (transform) задается автоматически при создании графического контекста так же, как цвет и шрифт. В дальнейшем его можно изменить методом setTransform() так же, как меняется цвет или шрифт. Аргументом этого метода служит объект класса AffineTransform из пакета java.awt.geom, подобно объектам класса Сolor или 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) в пространстве графического устройства.
Такое преобразование не искривляет плоскость — прямые линии переходят в прямые, углы между линиями сохраняются. Примерами аффинных преобразований служат повороты вокруг любой точки на любой угол, параллельные сдвиги, отражения от осей, сжатия и растяжения по осям.
Следующие два конструктора используют в качестве аргумента массив {а, b, с, d, e, f} или {a, b, c, d}, если e = f = 0, составленный из таких же коэффициентов в том же порядке:
AffineTransform(double[] arr)
AffineTransform(float[] arr)
Пятый конструктор создает новый объект по другому, уже имеющемуся, объекту:
AffineTransform(AffineTransform at)
Шестой конструктор — конструктор по умолчанию — создает тождественное преобразование:
AffineTransform()
Эти конструкторы математически точны, но неудобны при задании конкретных преобразований, например, рассчитать коэффициенты поворота на 57° вокруг точки с координатами (20, 40).
Во многих случаях удобнее создать преобразование статическими методами, возвращающими объект класса AffineTransform.
· getRotateInstance (double angle) — возвращает поворот на угол angle, заданный в радианах, вокруг начала координат. Положительное направление поворота таково, что точки оси Ох поворачиваются в направлении к оси Оу. Если оси координат пользователя не менялись преобразованием отражения, то положительное значение angle задает поворот по часовой стрелке.
· getRotateInstance(double angle, double x, double у) — такой же поворот вокруг точки с координатами (х, у).
· getScalelnstance (double sx, double sy) — изменяет масштаб по оси Ох в sx раз, по оси Оу — в sy раз.
· getSharelnstance(double shx, double shy)— преобразует каждую точку (x, у) в точку (x+shx*y, shy*x+y).
· getTranslateInstance (double tx, double ty)—сдвигаеткаждуюточку(х, у)вточку(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)
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), напротив, осуществляется после текущего преобразования.